Введение

Данное учебное пособие предназначено для демонстрации эффективных возможностей среды программирования AutoLISP® для AutoCAD® и для ознакомления с новыми функциями языка AutoLISP.

Здесь на примере построения парковой дорожки с помощью автоматизированных средств рисования, экономящих время разработчика, показаны преимущества параметрического программирования. Описано создание чертежной процедуры для автоматизации создания сложных форм—операции черчения, неоднократное выполнение которой вручную нежелательно.

Темы:

  • Работа в среде Visual LISP
  • Обзор учебного пособия
Ваш комментарий об этой странице


Работа в среде Visual LISP

Введение
Введение
Работа в среде Visual LISP

Данное учебное пособие предназначено для опытных пользователей AutoCAD. Предполагается, что они знакомы с языком LISP или AutoLISP. Также предполагается понимание основных задач управления файлами в ОС Windows®, например, таких, как создание папок, копирование файлов, и переход по файловой системе на жестком диске или в сети.

В данном пособии рассматриваются:

  • Среда Visual LISP®. Она позволяет редактировать и отлаживать код, а также предоставляет пользователю ряд других специальных средств разработки приложений AutoLISP.
  • Представлены функции ActiveX® и Reactor среды AutoLISP, а также несколько других дополнений к языку AutoLISP, предоставляемых VLISP.

Имеются два возможных способа выполнения упражнений данного учебного пособия:

  • Приложение можно выполнить по частям путем интерпретации LISP-файлов, загружаемых в один документ.
  • Можно скомпилировать программный код в VLX-приложение (исполняемый файл с расширением .vlx). VLX обладает независимым пространством имен и может взаимодействовать с документом, из которого загружается приложение.


Обзор учебного пособия

Введение
Введение
Обзор учебного пособия

Целью данного учебного пособия является создание новой команды AutoCAD, с помощью которой на экране строится парковая дорожка, выложенная круглой плиткой. Учебное пособие состоит из семи занятий. По изучения материала инструкции по выполнению отдельных задач постепенно становятся менее подробными. При возникновении вопросов следует обращаться к электронной справочной системе VLISP.

Занятия 4 и 5 предназначены для пользователей среднего уровня и выходят за рамки базовых концепций AutoLISP. Занятия 6 и 7 посвящены достаточно сложным задачам программирования и предназначены для опытных разработчиков AutoLISP.

Если AutoCAD устанавливался в полном варианте, то файлы исходного кода находятся в следующей папке:

<Папка AutoCAD>\Tutorial\VisualLISP\

Если продукт AutoCAD установлен без примеров, можно повторно запустить установку, выбрать вариант "Выборочная", а затем указать элемент "Учебное пособие".

Перед началом выполнения учебного пособия рекомендуется создать рабочую папку и скопировать в нее все файлы исходного кода AutoCAD. Это позволит при возникновении трудностей в ходе изучения учебного пособия восстановить файлы с правильным кодом. В тексте учебного пособия рабочая папка обозначается следующим образом:

<Папка AutoCAD>\Tutorial\VisualLISP\MyPath

Вместо этой строки в каждом конкретном случае следует вводить путь, по которому фактически находится рабочая папка.


Постановка задачи и начало программирования

На первом занятии определяется, какие задачи должно выполнять приложение. Используя среду разработки Visual LISP® (VLISP), будет создан файл LISP и начато написание кода AutoLISP® для поддержки приложения. В данном разделе будут описаны средства VLISP, облегчающие разработку приложений.

Темы:

  • Постановка задачи
  • Начало работы в Visual LISP
  • Знакомство с форматированием кода в Visual LISP
  • Анализ кода
  • Создание фиктивных функций в программе
  • Проверка кода с помощью Visual LISP
  • Выполнение программы в Visual LISP
  • Итоги занятия 1


Постановка задачи

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Постановка задачи

Разработка программы AutoLISP начинается с концепции автоматизации некоторых задач AutoCAD®. Часто возникает желание ускорить выполнение однотипных повторяющихся построений или объединить сложную последовательность операций в одну. Пусть необходимо построить парковую дорожку, которая имеет сложную конфигурацию с переменным числом компонентов, задаваемых пользователем. Дорожка должна выглядеть следующим образом:

Для построения дорожки программа на AutoLISP должна:

  • Нарисовать прямоугольную границу по начальной точке, конечной точке и ширине. Граница может быть расположена на плоскости произвольным образом и обладать произвольными размерами.
  • Запросить у пользователя размер плитки и расстояние между плитками. Плитки представляют собой круги; они должны заполнять дорожку, не перекрываясь и не пересекая границу.
  • Расположить плитки на дорожке в шахматном порядке.

Чтобы посмотреть, как программа должна работать, можно запустить готовую версию приложения, поставляемую с AutoCAD.

Для запуска имеющегося примера
  1. В меню "Сервис" AutoCAD выберите "Приложения".
  2. Выберите файл gardenpath.vlx из папки Tutorial\VisualLISP и нажмите кнопку "Загрузить".
  3. Нажмите кнопку "Закрыть".
  4. В командной строке введите gpath.
  5. В ответ на первые два запроса укажите начальную и конечную точки в области рисования AutoCAD.
  6. В ответ на запрос "Half Width of Path" (половина ширины дорожки) введите 50 (если работа ведется в британской системе единиц, следует ввести число 2).
  7. В диалоговом окне "Garden Path Tile Specifications" нажмите "OK".


Начало работы в Visual LISP

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Начало работы в Visual LISP

Теперь видно, как приложение должно работать, поэтому можно начать разработку программы VLISP. Но сначала необходимо понять, что происходит, когда среда VLISP ожидает передачи управления из AutoCAD. Возможно, при запуске примера уже возникала подобная ситуация.

Чтобы увидеть, как ведет себя Visual LISP, когда находится в режиме ожидания передачи управления из AutoCAD
  1. В меню "Сервис" AutoCAD выберите "Приложения".
  2. Выберите файл gardenpath.vlx из папки Tutorial\VisualLISP и нажмите кнопку "Загрузить".
  3. Нажмите кнопку "Закрыть".
  4. В командной строке AutoCAD введите vlisp для запуска Visual LISP.
  5. Переключитесь обратно в окно AutoCAD (для этого можно либо выбрать значок AutoCAD на панели задач, либо требуемое количество раз нажать ALT + TAB и выбрать AutoCAD) и в командной строке AutoCAD введите gpath.
  6. Перед тем как отвечать на подсказки команды gpath, переключитесь в окно VLISP.

    7. В окне VLISP указатель мыши заменяется специальным курсором VLISP, обозначающим, что ввод команд или текста в окне VLISP запрещен. Этот курсор напоминает, что для возобновления работы с VLISP необходимо завершить выполняющуюся в AutoCAD программу AutoLISP. Программу необходимо завершать каждый раз при появлении курсора VLISP

  7. Вернитесь в окно AutoCAD и ответьте на все запросы команды gpath.

Теперь можно приступить к созданию приложения для построения парковой дорожки.

Для начала разработки приложения в Visual LISP
  1. В меню "Файл" среды VLISP выберите "Создать файл" [image].
  2. В окне текстового редактора (окно с заголовком “<Без имени-0>”) введите следующий код. Комментарии можно пропустить:
    3. ;;; Функция C:GPath является основной функцией программы и определяет
    ;;; команда GPATH (команда среды AutoCAD).
    (defun C:GPath ()
      ;; Запрос на ввод: путь к местоположению и
      ;; направление, затем параметров пути. Продолжить только при
      ;; действительных входных данных.
      (if (gp:getPointInput) ;
        (if (gp:getDialogInput)
          (progn
            ;; В этом месте получены действительные входные данные от пользователя.
            ;; Начертить контур с сохранением результирующей полилинии
            ;; "указатель" в переменной вызывает PolylineName.
            (setq PolylineName (gp:drawOutline))
            (princ "\nФункция gp:drawOutline возвращает <")
            (princ PolylineName)
            (princ ">")
            (Alert "Поздравляем! Программа создана!")
          )
        (princ "\nОтмена функции.")
       )
       (princ "\nНедостаточно данных для создания границы.")
      )
      (princ) ; тихий выход
    )
    ;;; Отображение сообщения с именем команды.
    (princ "\nВведите gpath для создания парковой дорожки.")
    (princ)
  3. Выберите "Файл" » "Сохранить как" и сохраните код в новом файле <папка AutoCAD>\Tutorial\VisualLISP\MyPath\gpmain.lsp.
  4. Проверьте свою работу.


Знакомство с форматированием кода в Visual LISP

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Знакомство с форматированием кода в Visual LISP

VLISP распознает синтаксические единицы (символы и слова) текста программы AutoLISP и выделяет их различными цветами. Это упрощает и ускоряет процесс поиска ошибок. Например, если пропущена закрывающая кавычка, текст продолжает набираться фиолетовым, т.е. цветом, обозначающим заключенное внутри кавычек содержимое. Текст, идущий после закрывающей кавычки, окрашивается в зависимости от его назначения.

VLISP форматирует текст программы по мере его ввода, добавляя необходимые отступы и пробелы. Чтобы VLISP отформатировал код, скопированный из текстового редактора или другого файла, выберите в меню "Сервис" » "Форматировать код в редакторе".


Анализ кода

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Анализ кода

Выражение defun описывает новую функцию. Основная функция в данном случае называется C:GPath. Префикс C: обозначает, что к функции можно обращаться из командной строки AutoCAD. Для вызова приложения в ответ на подсказку AutoCAD "Команда:" необходимо ввести GPath. Для запроса у пользователя ввода информации используются функции gp:getPointInput и gp:getDialogInput. Границу дорожки строит функция gp:drawOutline. Эти функции имеют префикс gp:, обозначающий, что они используются только в приложении построения парковой дорожки. Подобные префиксы рекомендуется (хотя и не обязательно) использовать для того, чтобы отличать функции, используемые исключительно в рамках одного приложения, от совместно используемых функций и утилит.

С помощью выражений princ, находящихся в тексте главной функции, на экран выводится результат, если программа была выполнена успешно, или предупреждающее сообщение, если возникла непредвиденная ситуация. Например, как будет показано на втором занятии, если пользователь нажмет клавишу ENTER вместо указания точки на экране, выполнение функции gp:getPointInput прервется, а в главную функцию будет передано значение nil. При этом программа выдает сообщение princ “Недостаточно данных для создания границы”.

Вызов функции princ ближе к концу служит для вывода подсказки. После загрузки приложения в командной строке выдается подсказка, в которой говорится какое имя команды надо ввести для построения парковой дорожки. Затем princ без строкового аргумента вызывает тихое закрытие программы, то есть значение окончательного выражения основной функции не возвращается. При отсутствии последнего выражения princ подсказка выдавалась бы дважды.


Создание фиктивных функций в программе

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Создание фиктивных функций в программе

Для того чтобы программа выполнялась правильно, требуется еще три функции. Главная функция программы содержит вызовы трех пользовательских функций:

  • gp:getPointInput
  • gp:getUserInput
  • gp:drawOutline

Теперь необходимо создать три описания фиктивных функций. Фиктивные функции пока будут заменять полные функции, к разработке которых мы еще вернемся. Их наличие позволяет проверять работу программы до того, как в нее будут добавлены все элементы, необходимые для выполнения приложения.

Для описания фиктивной функции в приложении
  1. Поместите курсор в окне редактора в самое начало кода программы и нажмите пару раз ENTER для ввода пустых строк.
  2. Введите перед главной функцией следующее:
    3. ;;; Функция gp:getPointInput получает путь и размер
    (defun gp:getPointInput()
      (alert
        "Функция gp:getPointInput получает входные данные пользователя о черчении"
      )
      ;; Теперь возвращает T, как будто бы функция работает корректно.
      T
    )
    ;;; Функция gp:getDialogInput получает параметры пути
    (defun gp:getDialogInput ()
      (alert
        "Функция gp:getDialogInput запрашивает данные пользователя в диалоговом окне"
      )
      ;;Теперь возвращает T, как будто бы функция работает корректно.
      T
    )
    ;;; Функция gp:drawOutline создает контур пути
    (defun gp:drawOutline ()
      (alert
        (strcat "Данная функция создает контур полилинии "
         "\nи возвращает имя/указатель объекта полилинии."
        )
      )
      ;; Возврат кавычек. В завершении эта
      ;; функция возвращает имя или указатель объекта.
      'SomeEname
    )

Каждая функция содержит в конце строку, состоящую из одного символа T. Она используется в качестве возвращаемого значения. Каждая функция AutoLISP должна возвращать в вызвавшую ее функцию какое-либо значение. Буква T является обозначением значения “истина” в AutoLISP, при ее добавлении функция возвращает истинное значение. Файл gpmain.lsp организован таким образом, что для перехода к следующему этапу выполнения программы каждая вызываемая функция должна возвращать значение, отличное от nil (что означает “без значения”).

По умолчанию функции AutoLISP всегда возвращают значение последнего вычисленного внутри нее выражения. В фиктивных функциях единственным выражением является обращение к функции alert. Однако функция alert всегда возвращает значение nil. Таким образом, будучи вызванной последней в gp:getPointInput, она возвратит nil и переход через if к функции gp:getDialogInput станет невозможным.

По аналогичной причине функция gp:DrawOutline возвращает в качестве фиктивного значение ('SomeEname). Конструкции LISP, предваренные апострофом, не вычисляются. Для более подробного изучения языка LISP в конце данного учебного пособия приведен список рекомендуемых книг.


Проверка кода с помощью Visual LISP

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Проверка кода с помощью Visual LISP

VLISP обладает мощными инструментами проверки кода на синтаксические ошибки. Следует пользоваться ими перед выполнением программы. С помощью средств проверки можно выявить такие распространенные ошибки, как пропуск скобок или кавычек, а также другие случаи неверного синтаксиса.

Для синтаксической проверки кода
  1. Убедитесь, что окно текстового редактора, содержащее gpmain.lsp, является активным. Для активизации окна следует нажать кнопку мыши на его заголовке.
  2. В меню VLISP выберите "Сервис" » "Проверить текст в редакторе".

    3. Результаты синтаксической проверки выводятся в окне "Сообщения сборки". Если ошибок не обнаружено, окно содержит приблизительно следующее сообщение:

    [ПРОВЕРКА ТЕКСТА GPMAIN.LSP: загружается...]
    ......
    ; Проверка завершена.

При возникновении затруднений просмотрите раздел “Developing Programs with Visual LISP” ("Разработка программ в Visual LISP") в документе AutoLISP Developer's Guide ("Руководство разработчика AutoLISP"). Сначала следует попытаться установить причину ошибки самостоятельно. Если на поиск ошибки уходит слишком много времени, для продолжения работы над учебным пособием можно воспользоваться файлом примера gpmain.lsp, находящимся в папке lesson1.

Для использования файла примера gpmain.lsp (при необходимости)
  1. Закройте окно текстового редактора, содержащее файл gpmain.lsp с неработающим кодом.
  2. Выберите в меню VLISP "Файл" » "Открыть файл" и откройте файл gpmain.lsp, расположенный в папке \Tutorial\VisualLISP\lesson1.
  3. Выберите в меню "Файл" » "Сохранить как" и сохраните файл в папке \Tutorial\ VisualLISP\MyPath с именем gpmain.lsp, заменив созданную копию.


Выполнение программы в Visual LISP

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Выполнение программы в Visual LISP

В VLISP имеются специальные средства отладки и выявления ошибок, возникающих при выполнении приложения AutoLISP.

Для загрузки и выполнения программы
  1. В активном окне текстового редактора выберите в меню VLISP "Сервис" » "Загрузить текст в редактор" [image].
  2. В ответ на подсказку "_$" в окне консоли VLISP введите (C:GPath).

    3. Окно "Консоль Visual LISP" воспринимает ввод команд в формате AutoLISP, поэтому все имена функций должны заключаться в скобки.

  3. Нажимайте ENTER или кнопку "OK" в ответ на каждое выдаваемое сообщение. В завершение должно появиться сообщение “Congratulations - your program is complete! (Поздравляем! Создание программы завершено!)”

Примечание Если окно AutoCAD было свернуто, сообщения не будут видны. Поэтому перед вызовом функции gpath свернутое окно AutoCAD следует развернуть (с помощью панели задач или ALT + TAB).


Итоги занятия 1

Постановка задачи и начало программирования
Постановка задачи и начало программирования
Итоги занятия 1

На этом занятии были рассмотрены:

  • Постановка задачи.
  • Значения фиктивных функций.
  • Именование внутренних функций приложений, в отличие от совместно используемых функций.
  • Проверка кода с помощью VLISP.
  • Загрузка и выполнение программ в VLISP.

Занятие окончено. Сохраните программу, чтобы не потерять последние изменения в ней.


Средства отладки Visual LISP

В этом занятии будет описана технология использования средств отладки Visual LISP®, позволяющих ускорить разработку программ AutoLISP®. Кроме того, здесь объясняется разница между локальными и глобальными переменными и особенностями их применения. Создаваемая программа станет более интерактивной — т.к. в нее добавляется возможность запроса информации у пользователя. Информация будет хранится в списке. Будет показано, насколько эффективно использование списков в программах AutoLISP. Ведь именно благодаря возможностям обработки списков язык LISP получил свое имя (LISt Processing).

Темы:

  • Различия между локальными и глобальными переменными
  • Объединение данных в ассоциативные списки
  • Контроль значений переменных программы
  • Переработка кода программы
  • Комментарии в тексте программы
  • Точки останова и несколько контрольных значений
  • Итоги занятия 2


Различия между локальными и глобальными переменными

Средства отладки Visual LISP
Средства отладки Visual LISP
Различия между локальными и глобальными переменными

На этом занятии мы сравним локальные и глобальные переменные. Глобальные переменные доступны для всех функций, загруженных в документ (чертеж AutoCAD®). Эти переменные могут сохранять свое значение после завершения работы программы, в которой они были описаны. Этим свойством глобальных переменных можно при необходимости воспользоваться. Пример этого будет рассмотрен позднее.

Локальные переменные хранят свои значения, только пока выполняется функция, в которой они были определены. После завершения выполнения функции память автоматически освобождается от значений локальных переменных. Это называется автоматической очисткой памяти от мусора и является общим свойством большинства сред разработки на языке LISP, в том числе и VLISP. Локальные переменные используют память более эффективно, чем глобальные.

Другим большим преимуществом локальных переменных является то, что они упрощают отладку и усовершенствование приложений. Используя глобальные переменные, невозможно точно знать, когда или в какой функции значение переменной может измениться. Проследить за изменением локальной переменной значительно проще. Таким образом, локальные переменные позволяют сократить количество побочных эффектов (выражающихся в том, что одна часть программы влияет на ее другую часть).

Из-за перечисленных выше преимуществ мы практически везде в данном пособии будем использовать локальные переменные.

Примечание Возможно, что пользователи, уже работавшие с AutoLISP, привыкли использовать глобальные переменные для проверки работы программы в ходе ее создания. Теперь это уже не нужно, так как в VLISP имеются другие мощные средства отладки.

Темы:

  • Применение локальных переменных в программе
  • Изучение функции gp:getPointInput


Применение локальных переменных в программе

Средства отладки Visual LISP
Средства отладки Visual LISP
Применение локальных переменных в программе

Вернемся к функции gp:getPointInput, созданной на первом занятии.

(defun gp:getPointInput()
  (alert
    "Функция gp:getPointInput получает входные данные пользователя о черчении"
  )
  ;; Теперь возвращает T, как будто бы функция работает корректно.
  T
)

В том виде, как она есть на данный момент, функция не выполняет практически никаких действий. Нам нужно расширить ее, добавив функции ввода пользовательских данных (начальной и конечной точек, а также ширины дорожки).

При создании программ на AutoLISP желательно мысленно воссоздавать работу AutoCAD. Исходя из этих соображений лучше написать программу так, чтобы она запрашивала не ширину дорожки, а половину ширины, так как в последнем случае появляется возможность задать ее указанием точки на чертеже относительно осевой линии.

После завершения функции gp:getPointInput ее переменные, как и присвоенные им значения, становятся не нужны. Поэтому будем хранить значения, введенные пользователем, в локальных переменных. Функция должна выглядеть следующим образом:

(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
  (if (setq StartPt (getpoint "\nНачальная точка дорожки: "))
    (if (setq EndPt (getpoint StartPt "\nКонечная точка дорожки: "))
      (if (setq HalfWidth (getdist EndPt "\nПоловина ширины дорожки: "))
          T
      )
    )
  )
)

Локальные переменные объявляются после косой черты в выражении defun, с которого начинается описание функции. Сначала, при первом вызове getpoint, запрашивается начальная точка. Затем относительно нее задается конечная точка. При этом из начальной точки исходит динамическая, растягивающаяся при перемещении курсора линия (резиновая нить). Аналогично этому, при задании половины ширины резиновая нить исходит из конечной точки.

Для проверки работы gp:getPointInput
  1. Введите в окне консоли VLISP код функции gp:getPointInput.
  2. Поместив курсор в окне консоли за последней скобкой блока кода (или на следующей строке), нажмите ENTER для замены предыдущей загруженной версии функции gp:getPointInput.
  3. Вызовите функцию из окна консоли. Для этого в ответ на подсказку введите (gp:getPointInput).
  4. Укажите точки, запрашиваемые программой, и задайте половину ширины.


Изучение функции gp:getPointInput

Средства отладки Visual LISP
Средства отладки Visual LISP
Изучение функции gp:getPointInput

При вызове функции gp:getPointInput управление автоматически передается из VLISP в AutoCAD. После ответа на все три запроса управление передается из AutoCAD в VLISP, а в окне консоли выводится символ T.

Вот что происходит в программе:

  1. VLISP ожидает указания первой точки.
  2. После указания первой точки программа записывает ее значения (список из трех значений координат — X, Y и Z) в переменную StartPt.
  3. В первой функции if проверяется, было или не было введено правильное значение. После задания начальной точки управление передается следующей функции getpoint.
  4. После указания конечной точки значения ее координат сохраняются в переменной Endpt.
  5. Результат этого выражения проверяется в следующем выражении if, после чего управление передается функции getdist.
  6. Функция getdist позволяет как указывать точку на экране, так и вводить численное значение. Результат выполнения функции getdist хранится в переменной HalfWidth.
  7. После этого программа встречает значение T, находящееся внутри функции. Так как далее не следует никаких других функций, данная функция завершается и возвращает значение T. Именно оно (Т) и выводится в окне консоли.

Данные необходимо каким-то образом передать из одной функции в другую. Для этого можно, например, создать список значений, полученных функцией gp:getPointInput, как описано ниже:

(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
  (if (setq StartPt (getpoint "\nНачальная точка дорожки: "))
     (if (setq EndPt (getpoint StartPt "\nКонечная точка дорожки: "))
       (if (setq HalfWidth (getdist EndPt "\nполовина ширины дорожки: "))
 (list StartPt EndPt HalfWidth)
             )
     )
  )
)

Скопируйте этот вариант gp:getPointInput в окно консоли и нажмите ENTER. Теперь можно воспользоваться еще одной служебной функцией окна консоли.

Для выполнения gp:getPointInput с помощью протокола окна консоли
  1. Нажмите TAB.

    2. Консоль переходит в режим протокола, и в ней происходит циклический перебор ранее введенных команд. Для перебора команд в обратном порядке служит комбинация SHIFT+TAB.

  2. Когда в окне консоли появится подсказка (gp:getPointInput), нажмите ENTER для повторного выполнения функции.
  3. Ответьте на запросы, как и в предыдущий раз.

Функция возвращает список, содержащий в себе два вложенных списка и одно вещественное число (с плавающей точкой). Возвращаемые значения выглядят приблизительно следующим образом:

((4.46207 4.62318 0.0) (7.66688 4.62318 0.0) 0.509124)

Эти значения соответствуют переменным StartPt,EndPt и HalfWidth.


Объединение данных в ассоциативные списки

Средства отладки Visual LISP
Средства отладки Visual LISP
Объединение данных в ассоциативные списки

Хотя предыдущий пример работает, того же самого результата можно добиться другим, более удобным, способом. В следующем упражнении создается ассоциативный список (обрабатываемый LISP-функцией assoc). В ассоциативном списке с каждым элементом-значением связывается свой ключевой код. Ниже приведен пример ассоциативного списка:

((10 4.46207 4.62318 0.0) (11 7.66688 4.62318 0.0) (40 . 1.018248))

В этом списке ключевыми кодами являются числа 10, 11 и 40. Эти ключевые коды служат уникальным индексом списка. С помощью данного механизма AutoCAD возвращает в AutoLISP информацию об объектах, полученную при выполнении программы. Код 10 обозначает начальную точку, код 11 обычно используется для конечной точки.

В чем преимущество использования ассоциативного списка? В отличие от обычного списка, порядок значений в нем не играет никакой роли. Еще раз рассмотрим первый список:

((4.46207 4.62318 0.0) (7.66688 4.62318 0.0) 0.509124)

Присмотримся к возвращенным значениям. Непонятно, какой из вложенных списков соответствует начальной, а какой — конечной точке. Более того, внесение изменений в такую функцию может нежелательным образом повлиять на другие функции, использующие возвращаемые ею данные.

В ассоциативном списке порядок следования значений не важен. Даже при изменении порядка элементы ассоциативного списка всегда можно идентифицировать. Например, код 11 обозначает конечную точку вне зависимости от того, где она располагается в общем списке.

((11 7.66688 4.62318 0.0) ; order of list
 (40 . 1.018248) ; has been
 (10 4.46207 4.62318 0.0)) ; modified

Темы:

  • Применение ассоциативных списков
  • Сохранение результата функции gp:getPointInput в переменной


Применение ассоциативных списков

Средства отладки Visual LISP
Средства отладки Visual LISP
Применение ассоциативных списков

Значения ключевых кодов для ассоциативных списков необходимо документировать. Так, для парковой дорожки коды 10, 11, 40, 41 и 50 используются следующим образом:

  • 10 обозначает 3D координаты начальной точки дорожки.
  • 11 обозначает 3D координаты конечной точки дорожки.
  • 40 обозначает ширину (но не половину ширины) дорожки.
  • 41 обозначает длину дорожки от начала до конца.
  • 50 обозначает угол поворота дорожки.

Ниже приведен обновленный вариант функции gp:getPointInput. Здесь используется функция AutoLISP cons, которая создает кодированные вложенные списки, объединяемые затем в общий ассоциативный список. Теперь скопируйте этот вариант функции в окно консоли, нажмите ENTER и запустите функцию (gp:getPointInput) снова.

(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
  (if (setq StartPt (getpoint "\nНачальная точка дорожки: "))
    (if (setq EndPt (getpoint StartPt "\nКонечная точка дорожки: "))
      (if (setq HalfWidth (getdist EndPt "\nполовина ширины дорожки: "))
            ;; теперь можно создать ассоциативный список
            ;; (см. описание выше). Это будет значение, возвращаемое
            ;; функцией.
            (list
              (cons 10 StartPt)
              (cons 11 EndPt)
              (cons 40 (* HalfWidth 2.0))
              (cons 50 (angle StartPt EndPt))
              (cons 41 (distance StartPt EndPt))
                    )
      )
    )
  )
)

Следует обратить внимание на то, что при построении списка программа умножает половину ширины, указанную пользователем, на 2, преобразуя ее таким образом в полную ширину.

Полученный результат в окне консоли выглядит примерно так:

_$ (gp:getPointInput)

((10 2.16098 1.60116 0.0) (11 12.7126 7.11963 0.0) (40 . 0.592604) (50 . 0.481876) (41 . 11.9076))
_$


Сохранение результата функции gp:getPointInput в переменной ї

Средства отладки Visual LISP
Средства отладки Visual LISP
Сохранение результата функции gp:getPointInput в переменной ї

Теперь можно продвинуться дальше. Вызвав функцию снова, сохраним полученный результат в переменной gp_PathData. Для этого в ответ на подсказку в окне консоли введите следующее:

(setq gp_PathData (gp:getPointInput))

Для просмотра содержимого только что описанной переменной введите в окне консоли:

_$ gp_PathData

VLISP возвращает данные в следующем виде:

((10 2.17742 1.15771 0.0) (11 13.2057 7.00466 0.0) (40 . 1.12747) (50 . 0.487498) (41 . 12.4824))


Контроль значений переменных программы

Средства отладки Visual LISP
Средства отладки Visual LISP
Контроль значений переменных программы

VLISP предлагает разработчику целый комплекс средств программирования и отладки. Одним из наиболее полезных является средство контроля значений, позволяющее отслеживать состояние переменных более подробно, чем это позволяет сделать окно консоли. Можно также контролировать значения локальных переменных в процессе выполнения функции, в которой они определены.

Для контроля значений переменных
  1. Выберите в меню VLISP "Отладка" » "Добавить контрольное значение". Открывается диалоговое окно “Добавление контрольного значения”.

    2. Введите имя переменной для контроля ее значения. В данном случае введем переменную gp_PathData, только что описанную в окне консоли. Открывается диалоговое окно "Контрольное значение":

    VLISP отображает значение переменной в отдельной строке в окне "Контрольное значение" — основное окно показано на рисунке. Таким образом, длинные списки в окне целиком не умещаются. Окно контрольных значений можно растянуть, но можно поступить и иначе.

  2. Дважды нажмите кнопку мыши на имени переменной в окне "Контрольное значение". Открывается диалоговое окно "Изучение":

    3. В диалоговом окне "Изучение" показаны тип данных изучаемой переменной (в данном случае LIST—список) и ее значение. Каждый элемент списка выводится в этом диалоговом окне на отдельной строке.

  3. Дважды нажмите кнопку мыши на строке с кодом 11. Открывается еще одно диалоговое окно "Изучение":

  4. Просмотрев нужные переменные, закройте все диалоговые окна "Изучение", однако окно "Контрольное значение" оставьте открытым.


Переработка кода программы

Средства отладки Visual LISP
Средства отладки Visual LISP
Переработка кода программы

Теперь известно, как использовать ассоциативные списке в коде AutoLISP. Можно использовать этот метод для создания полной версии функции gp:getPointInput. Для этого следует заменить предыдущую версию gp:getPointInput, ранее сохраненную в файле gpmain.lsp, следующим кодом.

Примечание При наборе текста программы в файле gpmain.lsp вручную (а не копированием из имеющегося готового файла) можно сэкономить время, исключив все комментарии (строки, начинающиеся с точки запятой). Однако это не означает, что следует писать код без комментариев и в дальнейшем!

;;;--------------------------------------------------------------;
;;; Функция: gp:getPointInput ;
;;;--------------------------------------------------------------;
;;; Описание: С помощью этой функции пользователю предлагается выбрать три ;
;;; точки на чертеже, определяющие ;
;;; местоположение, направление и размер дорожки. ;
;;;--------------------------------------------------------------;
;;; Если пользователь вводит действительные данные, ;
;;; используйте startPt и endPt для определения положения, длины ;
;;; и угла, под которых создается дорожка. ;
;;;--------------------------------------------------------------;
;;; Эта функция возвращает список, который состоит из следующих элементов: ;
;;; (10 . Начальная точка) ;; Список из 3 действительных чисел (точка), определяющих ;
;;; ;; начальную точку парковой дорожки. ;
;;; (11 . Конечная точка) ;; Список из 3 действительных чисел (точка), определяющих ;
;;; ;; конечную точку парковой дорожки.  ;
;;; (40 . Ширина) ;; Действительное число, определяющее ширину ;
;;; ;; контура. ;
;;; (41 . Длина) ;; Действительное число, определяющее длину ;
;;; ;; контура. ;
;;; (50 . Угол дорожки) ;; Действительное число, определяющее угол ;
;;; ;; дорожки в радианах. ;
;;;--------------------------------------------------------------;
(defun gp:getPointInput(/ StartPt EndPt HalfWidth)
  (if (setq StartPt (getpoint "\nНачальная точка дорожки: "))
    (if (setq EndPt (getpoint StartPt "\nКонечная точка дорожки: "))
      (if (setq HalfWidth (getdist EndPt "\nполовина ширины дорожки: "))
         ;; теперь можно создать ассоциативный список
         ;; (см. описание выше).  Это будет значение, возвращаемое
         ;; функцией.
         (list
           (cons 10 StartPt)
           (cons 11 EndPt)
           (cons 40 (* HalfWidth 2.0))
           (cons 50 (angle StartPt EndPt))
           (cons 41 (distance StartPt EndPt))
) ) ) ) )

Теперь необходимо обновить основную функцию C:GPath в файле gpmain.lsp. Она должна выглядеть следующим образом:

(defun C:GPath (/ gp_PathData)
  ;; Запрос на ввод: путь к местоположению и
  ;; направление, затем параметров пути.  Продолжить только при
  ;; действительных входных данных. Сохраните данные в gp_PathData.
  (if (setq gp_PathData (gp:getPointInput))
    (if (gp:getDialogInput)
      (progn
            ;; В этом месте получены действительные входные данные от пользователя.
            ;; Начертить контур с сохранением результирующей полилинии
            ;; указатель" в переменной вызывает PolylineName.
            (setq PolylineName (gp:drawOutline))
            (princ "\nФункция gp:drawOutline возвращает <")
            (princ PolylineName)
            (princ ">")
            (Alert "Поздравляем! Программа создана!")
        ) ;_ end of progn
       (princ "\nОтмена функции.")
    ) ;_ end of if
    (princ "\nНедостаточно данных для создания границы.")
  ) ;_ end of if
 (princ) ; тихий выход
) ;_ end of defun

При копировании и вставке кода перед описанием функции C:GPath: следует добавить следующий заголовок:

;;;**************************************************************;
;;; Функция: C:GPath - основная функция для построения парковой дорожки ;
;;;--------------------------------------------------------------;
;;; Описание: Это основная функция для построения парковой дорожки. It is a ;
;;; C: функция означает, что она преобразуется в ;
;;; команда AutoCAD именуемая GPATH. Эта функция ;
;;; определяет общий поток программы парковой ;
;;; дорожки. ;
;;;**************************************************************;
;;; В переменной gp_PathData содержится ассоциативный список форм: ;
;;; (10 . Начальная точка) ;; Список из 3 действительных чисел (точка), определяющих ;
;;; начальную точку парковой дорожки.  ;
;;; (11 . Конечная точка) ;; Список из 3 действительных чисел (точка), определяющих ;
;;; конечную точку парковой дорожки. ;
;;; (40 . Ширина) ;; Действительное число, определяющее ширину ;
;;; контура.  ;
;;; (41 . Длина) ;; Действительное число, определяющее длину ;
;;; контура.  ;
;;; (50 . Угол дорожки) ;; Действительное число, определяющее угол ;
;;; дорожки в радианах.  ;
;;; (42 . Размер плитки) - Действительное число, определяющее размер ;
;;; (радиус) плиток парковой дорожки. ;
;;; (43 . Смещение плитки) - Промежуток между плитками. ;
;;; ( 3 . Стиль создания объектов) ;
;;; - Стиль создания объектов определяет, как ;
;;; вычерчиваются плитки. Ожидаемое ;
;;; значение является строкой, ;
;;; одно из трех значений (в строке ;
;;; не важно): ;
;;; "ActiveX" ;
;;; "Entmake" ;
;;; "Command" ;
;;; ( 4 . Стиль контура полилинии) ;
;;; - Стиль контура полилинии определяет;
;;; тип полилинии, используемый для ;
;;; контура дорожки. Ожидаемое значение,  ;
;;; одно из следующих (строковое;
;;; не важно): ;
;;; "Pline" ;
;;; "Light" ;
;;;**************************************************************;
Проверка переработанного кода
  1. Сохраните обновленный файл.
  2. Воспользуйтесь функцией "Проверить текст в редакторе" для проверки текста на наличие синтаксических ошибок.
  3. Отформатируйте текст программы для повышения читаемости кода.
  4. Загрузите код, чтобы переопределить предыдущие версии функций.
  5. Для выполнения программы в ответ на подсказку в окне консоли введите (c:gpath).

Если при выполнении программы возникли ошибки, следует попытаться их исправить и запустить программу снова. Если успешного выполнения программы не удается добиться даже после нескольких попыток, можно скопировать правильный код из папки Tutorial\VisualLISP\Lesson2.


Комментарии в тексте программы

Средства отладки Visual LISP
Средства отладки Visual LISP
Комментарии в тексте программы

Все выражения в программе на языке AutoLISP, начинающиеся с точки с запятой, считаются комментариями. В предыдущих двух примерах кода содержится множество таких комментариев. Комментарии в AutoLISP предназначены для самого разработчика, и не учитываются программой. Включение в текст программы комментариев является одним из признаков хорошего стиля программирования, к которому следует стремиться. Для чего же нужны комментарии?

  • Для того, чтобы код был понятен самому разработчику при редактировании программы и добавлении в нее новых возможностей месяцы спустя. Человеческой памяти свойственно быстро забывать подробности реализации алгоритмов, и даже самая очевидная последовательность функций через некоторое время может восприниматься как непонятный набор скобок.
  • Для того, чтобы код был понятен другим людям, несущим ответственность за дальнейшее сопровождение программы. Читать код, написанный другими, довольно утомительное занятие, особенно если в нем содержится мало комментариев.

В VLISP включены средства, помогающие комментировать код. Следует обратить внимание на то, что некоторые комментарии в примерах начинаются с трех точек с запятой (;;;), некоторые с двух (;;), а некоторые только с одной (;). Об обработке различных комментариев с помощью VLISP см. в разделе “Применение стилей комментариев Visual LISP” в Руководстве разработчика AutoLISP.

В целях экономии места в оставшихся исходных файлах примеров кода данного учебного пособия комментариев не содержится. Предполагается, что разработчик уже имеет хорошую привычку вставлять в текст программы подробные комментарии, и делает это без посторонней подсказки.


Точки останова и несколько контрольных значений

Средства отладки Visual LISP
Средства отладки Visual LISP
Точки останова и несколько контрольных значений

Точка останова это символ (точка), который помещается в исходный код для обозначения места, в котором выполнение программы временно приостанавливается. VLISP выполняет код до тех пор, пока не встретит точку останова. Обнаружив ее, VLISP переходит в режим ожидания указаний от программиста. Выполнение программы при этом не останавливается — она переводится в режим приостановленной анимации.

В режиме останова можно

  • Выполнять код последовательно: функцию за функцией, выражение за выражением.
  • Возобновлять выполнение программы из любой точки.
  • Динамически изменять значения переменных и результаты выполнения программы.
  • Добавлять переменные для контроля их значений.

Темы:

  • Панель инструментов "Отладка"
  • Перемещение по коду
  • Контроль значений переменных в ходе пошагового выполнения программы
  • Шаг с выходом из функции gp:getPointInput и переход к C:Gpmain


Панель инструментов "Отладка"

Средства отладки Visual LISP
Средства отладки Visual LISP
Панель инструментов "Отладка"

В данном разделе используются несколько инструментов, имеющихся на панели "Отладка". По умолчанию эта панель расположена в верхней части окна VLISP, в одном ряду с панелями "Вид" и "Сервис".

Панель "Отладка" располагается левее всех. Большинство кнопок панели активизируются только после запуска программы в отладочном режиме (т.е. если заданы одна или несколько точек останова).

Для удобства использования можно открепить панель инструментов "Отладка" от ее положения в верхней части экрана. Для этого нужно установить указатель мыши на две вертикальные линии в левой части панели и перетащить панель. Точно так же можно открепить и установить в удобное для работы место любую другую панель VLISP.

Панель "Отладка" разделена на три основные группы, в каждой из которых имеется по три кнопки. При выполнении программы в режиме отладки панель выглядит следующим образом:

  • Первые три кнопки позволяют выполнять код программы по шагам.
  • Следующие три кнопки определяют поведение VLISP после обнаружения точки останова или ошибки.
  • Следующие три кнопки служат для задания или удаления точек останова, добавления переменных для контроля их значений и перехода к месту последнего останова в исходном коде.

Последняя кнопка на панели "Отладка" является индикатором положения курсора. Она не выполняет никаких функций, а только наглядно показывает положение курсора при пошаговой отладке кода. При выполнении программы в обычном режиме (не в режиме отладки) на кнопке ничего не изображается.

Для задания точки останова
  1. В окне редактора VLISP, содержащем файл gpmain.lsp, установите указатель мыши перед открывающей скобкой функции setq в следующей строке кода внутри функции gp:getPointInput:
    2. (setq HalfWidth (getdist EndPt "\nполовина ширины дорожки: "))
  2. Нажмите один раз кнопку мыши. Курсор устанавливается в указанную позицию, как видно на следующей иллюстрации:

  3. Установив курсор в нужное положение, нажмите кнопку "Точка останова Вкл/Откл" на панели инструментов "Отладка".

    4. Кнопка "Точка останова Вкл/Откл" действует как переключатель между состояниями "Вкл" и "Откл". Если в положении курсора нет точки останова, то она ставится, если уже имеется удаляется.

  4. Чтобы загрузить файл, нажмите кнопку "Загрузка активного окна редактирования" на панели "Инструменты".
  5. Запустите функцию (C:GPath) из окна консоли VLISP.

    6. VLISP выполняет программу обычным образом вплоть до точки останова. В этом случае выводится запрос на ввод первых двух точек — начальной и конечной точек дорожки.

  6. Укажите в ответ на запросы начальную и конечную точки.

    7. После этого VLISP приостанавливает выполнение программы и переключается в окно текстового редактора. Строка, на которой произошел останов, выделяется.

Необходимо обратить внимание на следующее:

  • Курсор располагается прямо в точке останова. Это может оказаться не очевидным, поэтому VLISP предлагает еще одну подсказку.
  • На панели "Отладка" индикатор положения курсора принимает вид красного курсора перед парой скобок. Это означает, что работа отладчика VLISP была приостановлена перед выражением.


Перемещение по коду

Средства отладки Visual LISP
Средства отладки Visual LISP
Перемещение по коду

Для перемещения по коду используются три крайние слева кнопки панели "Отладка". Они служат для выполнения следующих действий: <$startrange>запуск и перемещение по коду

  • Шаг с заходом в выделенное выражение.
  • Переход к концу выделенного выражения (т.е. шаг с обходом).
  • Переход к концу текущей функции, внутри которой произошел останов (т.е. шаг с выходом).

Перед тем как сделать выбор, следует еще раз проверить статус выделенного выражения и положение курсора. В данном случае выделенное выражение включает в себя вложенную в setq функцию getdist, а курсор располагается в самом начале выделенного блока.

Пошаговое выполнение кода от точки останова
  1. Нажмите кнопку "Шаг с обходом".

    2. После этого управление передается AutoCAD и запрашивается ширина дорожки.

  2. Ответьте на запрос.

    3. После того как ширина задана, управление снова передается в VLISP. Следует обратить внимание на положение курсора на индикаторе.

    VLISP вычисляет выделенное выражение целиком и останавливается в конце выражения.

  3. Снова нажмите кнопку "Шаг с обходом". VLISP переходит к началу следующего блока кода и выделяет блок целиком.
  4. Теперь нажмите кнопку "Шаг с заходом" (не нажимайте кнопку "Шаг с обходом").

Примечание Если в ходе выполнения упражнения был сделан неправильный выбор или пропущены какие-либо шаги, его можно легко начать сначала. Для этого сначала следует нажать кнопку "Сброс" на панели "Отладка". Выполнение кода VLISP прекращается, и система VLISP возвращается на верхний уровень. Теперь можно начать все с пункта 1.

Выделенной становится первая функция cons, а VLISP останавливается непосредственно перед функцией (обратите внимание на индикатор положения курсора).


Контроль значений переменных в ходе пошагового выполнения программы

Средства отладки Visual LISP
Средства отладки Visual LISP
Контроль значений переменных в ходе пошагового выполнения программы

При пошаговом выполнении программы можно добавлять переменные в окно контрольных значений и изменять их значения.

Если окно контрольных значений не отображается на экране, для его вывода достаточно нажать на панели кнопку "Окно контрольных значений".

Если в окне контрольных значений все еще содержится переменная gp_PathData, следует нажать кнопку "Очистить окно", расположенную в верхней части окна.

Добавление переменной в окно контрольных значений
  1. Дважды нажмите кнопку мыши на любом вхождении StartPt в окне текстового редактора VLISP. Это имя переменной, изменения значения которой необходимо проследить.
  2. Нажмите кнопку "Добавить контрольное значение" в окне контрольных значений или нажмите правую кнопку мыши и выберите "Добавить контрольное значение" из контекстного меню.
  3. Повторите ту же процедуру для переменных EndPt и HalfWidth. Окно контрольных значений должно выглядеть приблизительно так:

При отладке некорректно работающей программы, используйте точки останова в сочетании с контролем значений для проверки соответствия значений переменных ожидаемым значениям.

Кроме того, пользователь может изменить значение переменной и проследить, как это повлияет на выполнение программы. Пусть, например, переменная halfwidth должна принимать только целые значения. Однако в результате неточного ввода точек ее значение может оказаться равным 1.94818.

Изменение значения переменной в ходе выполнения программы
  1. В ответ на подсказку в окне консоли введите следующее:
    2. (setq halfwidth 2.0)

    Следует обратить внимание на то, что значение в окне "Контрольное значение" изменилось. Но можно ли при этом быть уверенным, что именно это новое значение используется при создании ассоциативного списка (вложенный список с кодом 40)? Для проверки этого добавим еще одно выражение в окно контрольных значений.

  2. Выберите в меню VLISP "Отладка" » "Результат последнего вычисления".

    3. В окно "Контрольное значение" добавляется переменная с именем *Last-Value*. *Last-Value* представляет собой глобальную переменную, в которую VLISP автоматически записывает значение последнего вычисления.

  3. Выполняйте программу пошагово с помощью кнопок "Шаг с заходом" и "Шаг с обходом" до тех пор, пока не будет вычислено выражение, ответственное за построение вложенного списка для ширины. Это выражение представляет собой следующее:
    4. (cons 40 (* HalfWidth 2.0))

    Если значение переменной HalfWidth было установлено равным 2, то в результате вычислений выражение должно возвращать в окне контрольных значений (40 .


Шаг с выходом из функции gp:getPointInput и переход к C:Gpmain

Средства отладки Visual LISP
Средства отладки Visual LISP
Шаг с выходом из функции gp:getPointInput и переход к C:Gpmain

Еще один момент, требующий пояснений: что происходит со значениями локальных переменных при выходе из функции gp:getPointInput.

Выход из функции gp:getPointInput и переход к c:gpath
  1. Нажмите кнопку "Шаг с выходом".

    2. VLISP переходит к самому концу функции gp:getPointInput и останавливается непосредственно перед выходом.

  2. Нажмите кнопку "Шаг с заходом".

Управление передается функции c:gpmain, из которой функция gp:getPointInput была вызвана перед этим.

Проверим значения переменных в окне контрольных значений. Переменные endpt и StartPt являются локальными для функции gp:getPointInput и получают значение nil. VLISP автоматически освобождает память, занимаемую этими переменными. Третья локальная переменная HalfWidth также должна была бы принять значение nil, однако в ходе отладки ее значение было глобально заменено из окна консоли, поэтому в окне "Контрольное значение" для этой переменной остается значение 2.0. Переменная *Last-Value* выводит ассоциативный список, созданный функцией gp:getPointInput.

Наш первый сеанс отладки завершен. Не следует забывать, что программа по-прежнему находится в режие приостановленной анимации.

Завершение занятия
  1. Нажмите кнопку "Продолжить" на панели "Отладка". Ответьте на запросы. Это приводит к завершению работы программы.
  2. Выберите в меню VLISP "Отладка" » "Удалить все точки останова". На запрос ответьте “да”. Все точки останова удаляются из текста программы.

    3. Следует помнить, что для удаления отдельной точки останова следует поместить курсор в ее позицию в тексте программы и нажать кнопку "Точка останова Вкл/Откл".


Итоги занятия 2

Средства отладки Visual LISP
Средства отладки Visual LISP
Итоги занятия 2

На этом занятии были рассмотрены:

  • Локальные и глобальные переменные.
  • Задание и удаление точек останова.
  • Пошаговое выполнение программы.
  • Контроль и динамическое изменение значений переменных в процессе выполнения программы.
  • Сброс значений локальных переменных в nil после завершения выполнения функции, в которой они определены.

Программисты, планирующие разрабатывать приложения на AutoLISP, смогут убедиться, что инструменты, описанные на этом занятии, незаменимы в ежедневной работе.


Построение контура дорожки

В этом занятии возможности программы будут расширены, появится возможность вычерчивать в AutoCAD контур парковой дорожки в виде полилинии. Для построения контура необходимо предварительно написать несколько служебных функций, которые в дальнейшем могут быть применены и в других приложениях. Также будет показано, как создавать функции, принимающие аргументы — данные, передаваемые в функцию — и почему использование аргументов представляет собой эффективную концепцию программирования. К концу занятия можно будет создать объект в AutoCAD параметрически (т.е. динамически на основе уникальных параметров, заданных пользователем).

Темы:

  • Использование служебных функций
  • Создание объектов в AutoCAD
  • Создание функции построения границы дорожки
  • Итоги занятия 3


Использование служебных функций

Построение контура дорожки
Построение контура дорожки
Использование служебных функций

Служебные функции выполняют задачи, общие для различных создаваемых программистом приложений. Такие функции служат своеобразной библиотекой и могут использоваться снова и снова.

Служебные функции необходимо тщательно документировать. В комментариях следует также указывать, каким образом можно усовершенствовать функцию в будущем, если позволит время.

Темы:

  • Преобразование градусов в радианы
  • Преобразование 3D точек в 2D точки


Преобразование градусов в радианы

Построение контура дорожки
Построение контура дорожки
Преобразование градусов в радианы

Создадим функцию, которая в дальнейшем позволит не набирать многократно одну и ту же формулу. Она выглядит следующим образом:

(defun Degrees->Radians (numberOfDegrees)
(* pi (/ numberOfDegrees 180.0)))

Функция называется Degrees->Radians и служит для перевода углов, выраженных в градусах, в радианы.

Для чего необходимо переводить угловые величины в радианы? AutoCAD® использует измерение углов в радианах для отслеживания углов, хотя большинство людей привыкли измерять углы в градусах. Эта функция позволяет пользователю вводить значения в градусах, а AutoLISP® преобразует их в радианы.

Проверка работы служебной функции
  1. В ответ на подсказку в окне консоли VLISP введите следующее:
    2. (defun Degrees->Radians (numberOfDegrees)
    (* pi (/ numberOfDegrees 180.0)))
  2. В ответ на подсказку в окне консоли VLISP введите следующее:
    3. (degrees->radians 180)

    Функция возвращает число 3,14159. Таким образом, 180 градусов преобразуются в 3,14159 радиан.

Для того чтобы использовать функцию в программе, следует просто скопировать ее из окна консоли в файл gpmain.lsp. Функцию можно вставить в любое место файла; но, естественно, не в код какой-либо уже имеющейся функции.

Для форматирования вставленного текста выделите текст, а затем нажмите кнопку "Форматирование выделенного фрагмента". VLISP выполнит форматирование кода с соответствующими отступами.

Теперь добавим комментарии, описывающие функцию. После документирования код функции должен выглядеть примерно так:

;;;--------------------------------------------------------------;
;;; Функция: Degrees->Radians;
;;;--------------------------------------------------------------;
;;; Описание: Данная функция преобразует значение, представленное ;
;;; в градусах, в радианы ;
;;; . При проверке параметра ;
;;; numberOfDegrees ошибок не обнаружено -- ожидается, что парамер всегда ;
;;; будет действительным числом. ;
;;;--------------------------------------------------------------;
(defun Degrees->Radians (numberOfDegrees)
  (* pi (/ numberOfDegrees 180.0))
)


Преобразование 3D точек в 2D точки

Построение контура дорожки
Построение контура дорожки
Преобразование 3D точек в 2D точки

Другая полезная функция, которая потребуется в программе построения парковой дорожки, должна преобразовывать 3D точки в 2D точки. AutoCAD обычно работает с трехмерными координатами, однако некоторые объекты (например, компактные полилинии) по определению являются двумерными. Функция getpoint возвращает 3D точки, поэтому для их преобразования требуется создать специальную функцию.

Преобразование 3D точек в 2D
  1. В ответ на подсказку в окне консоли введите следующее:
    2. (defun 3dPoint->2dPoint (3dpt)(list (car 3dpt) (cadr 3dpt)))
  2. Для этого в ответ на подсказку в окне консоли введите:
    3. (3dpoint->2dpoint (list 10 20 0))

    Функция работает; однако, для создания приложения, строящего парковую дорожку, следует учитывать еще одно обстоятельство. Хотя в функциях LISP часто не важно, является число целым или действительным, для функций ActiveX это не так. Подробнее это будет описано дальше. Функции ActiveX работают с вещественными числами. Нашу функцию можно легко модифицировать так, чтобы она всегда возвращала вещественные числа, а не целые.

  3. В ответ на подсказку в окне консоли введите следующее:
    4. (defun 3dPoint->2dPoint (3dpt)(list (float(car 3dpt))
    (float(cadr 3dpt))))
  4. Снова запустите функцию:
    5. (3dpoint->2dpoint (list 10 20 0))

    Следует обратить внимание на то, что возвращаемые значения теперь являются вещественными числами (т.е. записываются с десятичной точкой).

  5. Снова проверим работу функции, на этот раз с помощью функции getpoint. В ответ на подсказку в окне консоли введите следующее:
    6. (setq myPoint(getpoint))
  6. Укажите точку в области рисования AutoCAD.

    7. Функция getpoint возвращает 3D точку.

  7. В ответ на подсказку в окне консоли введите следующее:
    8. (3dPoint->2Dpoint myPoint)

    На этот раз возвращается 2D точка.

    Теперь добавьте функцию в файл gpmain.lsp так же, как была добавлена функция Degrees->Radians. Новый код должен выглядеть следующим образом:

    ;;;--------------------------------------------------------------;
    ;;; Функция: 3dPoint->2dPoint ;
    ;;;--------------------------------------------------------------;
    ;;; Описание: Эта функция обрабатывает один параметр, представляющий ;
    ;;; 3D точку (список из трех целых или действительных числе) и ;
    ;;; преобразовывает его в 2D точку (список из двух действительных чисел).;
    ;;; При проверке параметра 3D точки ошибок не обнаружено ;
    ;;; -- предполагается, это действительная точка. ;
    ;;;--------------------------------------------------------------;
    ;;; Действие: Добавьте функцию проверки параметров, чтобы ;
    ;;; функция не приводила к сбою программы при передаче ;
    ;;; значения null или другого типа данных, отличных от ;
    ;;; 3D точки. ;
    ;;;--------------------------------------------------------------;
    (defun 3dPoint->2dPoint (3dpt)
      (list (float(car 3dpt)) (float(cadr 3dpt)))
    )

    Обратите внимание, что заголовок содержит указания о дальнейшей доработке данной функции. Для того чтобы приобрести дополнительный опыт, можно подумать о том, как усовершенствовать функцию, чтобы защитить функцию от аварийного завершения из-за неверного ввода данных.

    Совет: numberp и listp functions…

    (listp '(1 1 0)) => T
    (numberp 3.4) => T


Создание объектов в AutoCAD

Построение контура дорожки
Построение контура дорожки
Создание объектов в AutoCAD

В большинстве программ на AutoLISP для создания объектов используются один из следующих методов:

  • функции ActiveX
  • Функция entmake
  • Функция command

В этом занятии описано создание объектов с помощью ActiveX® . На 5 занятии будут использоваться функция entmake и альтернативы команды AutoCAD.

Темы:

  • Создание объектов с помощью функций ActiveX
  • Создание объектов с помощью entmake
  • Использование командной строки AutoCAD


Создание объектов с помощью функций ActiveX

Построение контура дорожки
Построение контура дорожки
Создание объектов с помощью функций ActiveX

Создание объектов в VLISP с помощью функций ActiveX является самым современным из имеющихся методов. ActiveX обладает рядом преимуществ по сравнению с использованием функций entmake и command.

  • Функции ActiveX работают быстрее.
  • Имена функций ActiveX обозначают действие, которые они выполняют, что обеспечивает более удобное чтение, обновление и исправление программы.

Пример функции ActiveX приводится ниже в тексте этого же занятия.


Создание объектов с помощью entmake

Построение контура дорожки
Построение контура дорожки
Создание объектов с помощью entmake

Функция entmake собирает в ассоциативный список информацию о координатах и ориентации, слое и цвете объекта, а затем передает эти данные в AutoCAD для построения объекта. Ассоциативный список функции entmake очень похож на список, получаемый функцией entget. Различие заключается в том, что entget возвращает информацию об объекте, а entmake по имеющимся данным строит новый объект.


Использование командной строки AutoCAD

Построение контура дорожки
Построение контура дорожки
Использование командной строки AutoCAD

Когда AutoLISP впервые был встроен в AutoCAD, единственным средством для создания объектов была функция command. Она позволяет программисту вызывать из программы на AutoLISP практически любую команду, которая может быть выполнена из командной строки AutoCAD. Это надежный способ, но он не настолько быстр, как функции ActiveX, и не обеспечивает такой же гибкости, как entmake.


Создание функции построения границы дорожки

Построение контура дорожки
Построение контура дорожки
Создание функции построения границы дорожки

После выполнения упражнений предыдущих занятий функция gp:drawOutline выглядела следующим образом:

;;;--------------------------------------------------------------;
;;; Функция: gp:drawOutline ;
;;;--------------------------------------------------------------;
;;; Описание: С помощью этой функции можно создать контур ;
;;; парковой дорожки. ;
;;;--------------------------------------------------------------;
(defun gp:drawOutline ()
  (alert
    (strcat "Данная функция создает контур полилинии "
      "\nи возвращает имя/указатель объекта полилинии."
    )
  )
  ;; Теперь она просто возвращает символ в кавычках. В завершении эта
  ;; функция возвращает имя или указатель объекта.
  'SomeEname
)

В том виде, как она есть, функция не выполняет практически никаких действий. Однако, ассоциативный список, хранящийся в переменной gp_PathData, позволяет рассчитать все характерные точки, необходимые для построения контура дорожки. Теперь определим, каким образом информация из этой переменной будет передаваться в функцию gp:drawOutline.

Следует помнить, что переменная gp_PathData является локальной и определена внутри функции C:GPath. В AutoLISP локальные переменные, описанные в одной функции, доступны всем функциям, вызываемым из нее (подробнее см. раздел Различия между локальными и глобальными переменными). Функция gp:drawOutline вызывается из C:GPath. Поэтому переменную gp-PathData можно использовать и для функции gp:drawOutline; однако это не является наилучшим решением.

Почему так? Если одна и та же переменная используется только двумя функциями, описанными в одном файле (как в приведенном примере), установить, где была описана и для чего используется переменная, нетрудно. Если функции определены в различных файлах, как это часто бывает, необходимо выполнить поиск в обоих файлах и выяснить, что представляет собой переменная gp_PathData.

Темы:

  • Передача параметров функциям
  • Работа с ассоциативным списком
  • Использование углов и задание точек
  • Вызов функций ActiveX из функции gp:drawOutline
  • Загрузка среды ActiveX
  • Получение указателя на пространство модели
  • Создание массива вершин полилинии
  • Преобразование списка точек в массив-вариант
  • Окончательная компоновка


Передача параметров функциям

Построение контура дорожки
Построение контура дорожки
Передача параметров функциям

Лучший способ передать информацию из одной функции в другую это передать вызываемой функции параметры (аргументы). Разработаем функцию таким образом, чтобы она ожидала передачи аргументов. Вспомним функцию Degrees->Radians. Ей передавался параметр numberOfDegrees:

(defun Degrees->Radians (numberOfDegrees)
(* pi (/ numberOfDegrees 180.0)))

При вызове функции необходимо передать ей число. Число внутри функции Degrees->Radians объявляется параметром, называемым numberOfDegrees. Например:

_$ (degrees->radians 90)

1.5708

В данном случае параметру numberOfDegrees присваивается число 90.

Можно также передать функции аргумент, являющийся переменной. Например, пусть имеется переменная aDegreeValue, содержащая число 90. Следующие команды задают aDegreeValue и передают переменную в функцию Degrees->Radians:

_$ (setq aDegreeValue 90)

90
_$ (degrees->radians aDegreeValue)

1.5708


Работа с ассоциативным списком

Построение контура дорожки
Построение контура дорожки
Работа с ассоциативным списком

Ассоциативный список, содержащийся в переменной gp_PathData, можно передать функции gp:drawOutline следующим образом:

(gp:drawOutline gp_PathData)

Это достаточно просто. Однако в тексте функции необходимо указать, каким образом информация из ассоциативного списка должна обрабатываться. Выяснить структуру списка можно с помощью средства VLISP "Изучить".

Для анализа ассоциативного списка с помощью средства VLISP "Изучить"
  1. Загрузите код из окна редактора.
  2. В ответ на подсказку в окне консоли введите следующее выражение:
    3. (setq BoundaryData (gp:getPointInput))

    VLISP будет записывать информацию в переменную BoundaryData.

  3. Ответьте на запросы о начальной и конечной точках и полуширине.
  4. Выберите имя переменной BoundaryData в окне консоли, дважды нажав на нем кнопку мыши.
  5. Выберите в меню VLISP "Вид" » "Изучить".

    6. VLISP выводит следующее окно:

    В окне "Изучение" выводится каждый вложенный список переменной BoundaryData.

  6. В ответ на подсказку в окне консоли VLISP введите следующее:
    7. (assoc 50 BoundaryData)

    Функция assoc возвращает элемент ассоциативного списка, обозначенный определенным кодом. В данном примере указан код 50, соответствующий углу наклона парковой дорожки (см. раздел Применение ассоциативных списков).

  7. В ответ на подсказку в окне консоли VLISP введите следующее:
    8. (cdr(assoc 50 BoundaryData))

    Функция cdr возвращает второй и все следующие за ним элементы списка. В данном примере cdr находит значение угла, которое является вторым и последним элементом значения, возвращаемого функцией assoc.

    После этих объяснений при понимании следующего фрагмента кода затруднения возникать не должны:

      (setq PathAngle (cdr (assoc 50 BoundaryData))
            Width (cdr (assoc 40 BoundaryData))
            HalfWidth (/ Width 2.00)
            StartPt (cdr (assoc 10 BoundaryData))
            PathLength (cdr (assoc 41 BoundaryData))


Использование углов и задание точек

Построение контура дорожки
Построение контура дорожки
Использование углов и задание точек

Есть еще пара вопросов, которые требуется рассмотреть. Во-первых, необходимо установить, каким образом строится дорожка под заданным пользователем углом. Из функции gp:getPointInput можно легко получить исходный угол наклона дорожки. Для построений нужно рассчитать два вектора, перпендикулярных этому углу.

Здесь пригодится функция Degrees->Radians. Следующий фрагмент кода демонстрирует, как можно задать два перпендикулярных вектора с помощью переменной PathAngle, передаваемой в качестве аргумента в функцию Degrees->Radians:

(setq angp90 (+ PathAngle (Degrees->Radians 90))
      angm90 (- PathAngle (Degrees->Radians 90)))

Имеющиеся данные позволяют найти с помощью функции polar четыре угловые точки дорожки.

(setq p1 (polar StartPt angm90 HalfWidth)
      p2 (polar p1 PathAngle PathLength)
      p3 (polar p2 angp90 Width)
      p4 (polar p3 (+ PathAngle (Degrees->Radians 180))

Функция polar возвращает 3D точку, расположенную под заданным углом и на заданном расстоянии от исходной точки. Например, точку p2 функция polar находит, откладывая от точки p1 расстояние, равное PathLength, вдоль вектора, повернутого на угол PathAngle против часовой стрелки относительно оси X.


Вызов функций ActiveX из функции gp:drawOutline

Построение контура дорожки
Построение контура дорожки
Вызов функций ActiveX из функции gp:drawOutline

Функция gp:drawOutline обращается к ActiveX для отображения полилинии границы дорожки в AutoCAD. Следующий фрагмент кода использует ActiveX для создания границы:

;; Добавление полилинии к пространству модели автоматически с использованием ActiveX.
(setq pline (vla-addLightweightPolyline
*ModelSpace*; Глобальное определение пространства модели
VLADataPts; вершины границы дорожки
) ;_ end of vla-addLightweightPolyline
 ) ;_ end of setq
(vla-put-closed pline T)

Что означает этот фрагмент кода? Ценным источником информации для его понимания служит электронный документ ActiveX and VBA Reference ("Справочник по ActiveX и VBA"). В нем описываются методы и свойства, доступные клиентам ActiveX, каковым и является наше приложение, строящее парковую дорожку. В разделе “Working with ActiveX” ("Использование ActiveX") в документе AutoLISP Developer's Guide ("Руководство разработчика AutoLISP") объясняется, как преобразовать синтаксис VBA™ в документе ActiveX and VBA Reference ("Справочник по ActiveX и VBA") в вызовы ActiveX в синтаксисе AutoLISP.

На текущем этапе начальное представление о работе этого фрагмента программы можно получить, внимательно изучив в нем структуру двух вызовов функций vla-. Имена всех функций AutoLISP ActiveX, работающих с объектами AutoCAD, начинаются с vla-. Например, если метод ActiveX имеет имя addLightweightPolyline, то vla-addLightweightPolyline это функция, обращающаяся к этому методу. Вызов vla-put-closed служит для обновления свойства замыкания объекта pline - полилинии, созданной с помощью vla-addLightweightPolyline.

Имеется несколько стандартных правил, которым подчиняются вызовы ActiveX из AutoLISP:

  • Первым аргументом вызовов методов vla-put, vla-get, и vla- является изменяемый или искомый объект, например, *ModelSpace* в первом вызове или pline во втором.
  • Вызов метода vla- возвращает VLA-объект, который может быть использован для последующих вызовов. Например, vla-addLightweightPolyline выдает объект pline (плинию), который модифицируется следующим вызовом ActiveX.
  • Объектная модель ActiveX имеет иерархическую структуру. Объекты обрабатываются, начиная от самого верхнего уровня приложения до отдельных графических объектов — например, полилиний и кругов. Таким образом, на данном этапе разработка функции gp:drawOutline еще не закончена, так как доступ к ActiveX-объекту *ModelSpace* должен осуществляться только через главный объект приложение.


Загрузка среды ActiveX

Построение контура дорожки
Построение контура дорожки
Загрузка среды ActiveX

Функции ActiveX не загружаются автоматически при запуске AutoCAD или VLISP, поэтому необходимо убедиться, что ActiveX загружен. Для этого служит следующий вызов:

(vl-load-com)

Функция vl-load-com инициализирует ActiveX-среду AutoLISP. Если эта среда уже загружена, vl-load-com не выполняет ничего.


Получение указателя на пространство модели

Построение контура дорожки
Построение контура дорожки
Получение указателя на пространство модели

При работе с функциями ActiveX необходимо иметь возможность переключаться между пространством модели и пространством листа, в которых могут быть созданы объекты. (в терминологии ActiveX слово "примитивы" употребляется в значении объекты, однако в данном пособии употребляется термин "объект"). Для задания пространства, в котором требуется создать объект, нужно получить от AutoCAD указатель на это пространство. К сожалению, получение указателя на пространство модели осуществляется не в один этап. Следующий фрагмент кода показывает, как именно это делается:

(vla-get-ModelSpace (vla-get-ActiveDocument
  (vlax-get-Acad-Object)))

Функция vlax-get-Acad-Object работает внутри программы и возвращает указатель на AutoCAD. Указатель передается в функцию vla-get-ActiveDocument, которая затем возвращает его в активный чертеж (документ) AutoCAD. Указатель "Активный документ" затем передается в функцию vla-get-ModelSpace, которая затем возвращает его в пространство модели текущего чертежа.

Естественно, каждый раз набирать это выражение не очень удобно. Например, посмотрим, насколько усложняется код для построения полилинии с помощью ActiveX, если в него включить целиком выражение для указателя на пространство модели:

(setq pline (vla-addLightweightPolyline
               (vla-get-ModelSpace
                 (vla-get-ActiveDocument
                    (vlax-get-Acad-Object)
                 )
               )
               VLADataPts)
)
(vla-put-closed pline T)

Функция становится менее понятной. И дело не только в этом. Теперь при создании каждого объекта необходимо будет повторять одну и ту же последовательность вложенных функций. Это — один из тех немногих случаев, когда уместно использовать глобальные переменные. Приложение может строить множество объектов в пространстве модели (вспомним о плитках, которыми должна быть вымощена дорожка), поэтому объявим глобальную переменную для сохранения указателя на пространство модели:

(setq *ModelSpace* (vla-get-ModelSpace (vla-get-ActiveDocument
                 (vlax-get-Acad-Object))))

Переменную *ModelSpace* можно использовать в вызове любой ActiveX-функции, предназначенной для создания объектов. Единственное, на что следует обратить внимание это то, что переменная *ModelSpace* должна быть определена до начала создания графических объектов. Поэтому функция setq, присваивающая значение данной переменной, должна выполняться в процессе загрузки, сразу же после функции vl-load-com. Вызовы этих функций следует поместить в тексте программы до самого первого объявления defun. В результате они будут выполняться на самом раннем этапе, в процессе загрузки файла.


Создание массива вершин полилинии

Построение контура дорожки
Построение контура дорожки
Создание массива вершин полилинии

Теперь нужно решить последнюю задачу: Теперь нужно решить последнюю задачу: преобразовать отдельные точечные переменные p1, p2, p3 и p4 в формат, необходимый для вызова функции vla-addLightweightpolyline. Для начала обратимся к справочной информации.

Получение информации о функции
  1. Нажмите кнопку "Справка" на панели инструментов VLISP.
  2. Введите vla-addLightweightpolyline в диалоговом окне "Введите имя пункта" и нажмите "ОК". Справочная система не учитывает регистр символов, поэтому можно не заботиться о расстановке заглавных букв.

В справочной системе говорится, что для AddLightWeightPolyline вершины полилинии должны быть представлены в виде массива типа "вариант" (универсальный тип), хранящего числа с двойной точностью. В справочной системе по этому поводу говорится следующее:

Массив 2D координат (в МСК), задающий вершины полилинии. Для построения компактной полилинии нужно не меньше двух точек (т.е. четырех элементов). Размер массива должен быть кратным 2.

В ActiveX тип "вариант" используется для хранения произвольных типов данных. Типом "вариант" могут быть представлены строки, целые числа и массивы. Данные в типе "вариант" хранятся вместе с идентифицирующей их информацией.

Итак, у нас имеется четыре точки, каждая в формате (x, y, z). Задача заключается в том, чтобы преобразовать их в список следующего вида:

(x1 y1 x2 y2 x3 y3 x4 y4)

Функция append объединяет несколько списков в один. Для создания списка из четырех точек в формате функции ActiveX используется следующее выражение:

(setq polypoints (append (3dPoint->2dPoint p1)
                              (3dPoint->2dPoint p2)
                              (3dPoint->2dPoint p3)
                              (3dPoint->2dPoint p4)))

Писать функцию 3dPoint->2dPoint четыре раза не очень удобно. Сократить выражение можно с помощью функций mapcar и apply. Функция mapcar выполняет операции над отдельными элементами одного или более списков, а функция apply передает список аргументов заданной функции. В итоге код должен выглядеть следующим образом:

(setq polypoints (apply 'append (mapcar '3dPoint->2dPoint
(list p1 p2 p3 p4))))

Таким образом, перед вызовом функции mapcar список точек примет вид:

((x1 y1 z1) (x2 y2 z2) (x3 y3 z3) (x4 y4 z4))

После выполнения mapcar получается список точек следующего вида:

((x1 y1) (x2 y2) (x3 y3) (x4 y4))

И, наконец, после применения функции append список, полученный из mapcar, приобретает следующий вид:

(x1 y1 x2 y2 x3 y3 x4 y4)


Преобразование списка точек в массив-вариант

Построение контура дорожки
Построение контура дорожки
Преобразование списка точек в массив-вариант

Таким образом, данные о вершинах полилинии теперь сгруппированы в список, формат которого подходит для передачи его в другие функции AutoLISP. Но нам нужно использовать их как входной параметр для вызова функции ActiveX, которая требует на входе массив-вариант, хранящий числа с двойной точностью. Для преобразования списка в тип "вариант" можно воспользоваться следующей служебной функцией:

(defun gp:list->variantArray (ptsList / arraySpace sArray)
  ; выделенное пространство для массива 2d точек, записанных в виде массивов типа "double"
  (setq arraySpace (vlax-make-safearray
              vlax-vbdouble ; тип элемента
              (cons 0
                    (- (length ptsList) 1)
                    ) ; размер массива
              )
)
  (setq sArray (vlax-safearray-fill arraySpace ptsList))
  ; возврат массива-варианта
  (vlax-make-variant sArray)
  )

Внутри функции gp:list->variantArray выполняются следующие действия:

  • Функция vlax-make-safearray вызывается для выделения памяти для массивов чисел с двойной точностью (vlax-vbdouble). Функция vlax-make-safearray также требует здания нижней и верхней границ индекса массива. В функции gp:list->variantArray вызов vlax-make-safearray определяет начальный индекс, равный 0, и задает верхний предел, на единицу меньший количества элементов, переданных в (ptsList).
  • Функция vlax-safearray-fill заполняет массив элементами списка точек.
  • Функция vlax-make-variant преобразует массив в массив-вариант. Так как это последняя функция в gp:list->variantArray, возвращаемое ей значение передается наверх в вызывающую процедуру.

Далее представлен пример вызова функции, обращающейся к gp:list->variantArray для преобразования списка в массив-вариант, содержащий числа с двойной точностью:

; преобразование данных из списка в массив-вариант
(setq VLADataPts (gp:list->variantArray polypoints))


Окончательная компоновка

Построение контура дорожки
Построение контура дорожки
Окончательная компоновка

Теперь все готово для построения границы парковой дорожки.

Для обновления кода
  1. Замените старый код функции gp:drawOutline следующим фрагментом:
    2. ;;;---------------------------------------------------------------
    ;;; Функция: gp:drawOutline
    ;;;---------------------------------------------------------------
    ;;; Описание: С помощью этой функции можно создать границу парковой
    ;;; дорожки.
    ;;;---------------------------------------------------------------
    ;;; Примечание: Проверка на наличие ошибок и коррекция не выполняются для
    ;;; параметра BoundaryData. Последовательность элементов в этом
    ;;; параметре не имеет значения, предполагается, что все представленные вложенные списки
    ;;; имеют действительные данные.
    ;;; --------------------------------------------------------------
    (defun gp:drawOutline (BoundaryData / VLADataPts PathAngle
           Width HalfWidth StartPt PathLength
           angm90 angp90 p1 p2
           p3 p4 polypoints pline
          )
      ;; extract the values from the list BoundaryData
      (setq PathAngle (cdr (assoc 50 BoundaryData))
    Width (cdr (assoc 40 BoundaryData))
    HalfWidth (/ Width 2.00)
    StartPt (cdr (assoc 10 BoundaryData))
    PathLength (cdr (assoc 41 BoundaryData))
    angp90 (+ PathAngle (Degrees->Radians 90))
    angm90 (- PathAngle (Degrees->Radians 90))
    p1 (polar StartPt angm90 HalfWidth)
    p2 (polar p1 PathAngle PathLength)
    p3 (polar p2 angp90 Width)
    p4 (polar p3 (+ PathAngle (Degrees->Radians 180)) PathLength)
    polypoints (apply 'append
      (mapcar '3dPoint->2dPoint (list p1 p2 p3 p4))
       )
      )
      ;; ***** преобразование данных *****
      ;; Обратите внимание, что polypoints записана в формате AutoLISP, состоящем из списка
      ;; 4 угловых точек парковой дорожки.
      ;; Переменную необходимо преобразовать в форму входного параметра,
      ;; допустимого для вызовов ActiveX.
      (setq VLADataPts (gp:list->variantArray polypoints))
      ;; Добавление полилинии к пространству модели автоматически с использованием ActiveX.
      (setq pline (vla-addLightweightPolyline
    *ModelSpace*; Глобальное определение пространства модели
    VLADataPts
     ) ;_ end of vla-addLightweightPolyline
      ) ;_ end of setq
      (vla-put-closed pline T)
      ;; Возврат имени объекта ActiveX для контура полилинии
      ;; Возвращаемое значение должно выглядеть примерно так:
      ;; #<VLA-OBJECT IAcadLWPolyline 02351a34>
      pline
    ) ;_ end of defun

    Обратите внимание на то, что gp:drawOutline теперь возвращает переменную pline, а не символ с апострофом 'SomeEname, как было в фиктивной версии функции.

  2. Отформатируем введенный код. Для этого выделите его и нажмите кнопку "Форматирование выделенного фрагмента" на панели инструментов VLISP.
  3. Добавим код для загрузки ActiveX и опишем глобальную переменную, хранящую указатель на пространство модели, как это было описано выше. Для этого установите курсор в верхней части окна текстового редактора и добавьте перед первым выражением defun следующий фрагмент:
    4. ;;;--------------------------------------------------------------
    ;;; Сначала необходимо загрузить функции ActiveX. Если поддержка ActiveX
    ;;; уже имеется в документе (если программа Bonus
    ;;; загружена в AutoCAD), ничего не произойдет. В противном случае загружается
    ;;; поддержка ActiveX.
    ;;;---------------------------------------------------------------
    (vl-load-com)
    ;;; На занятии 4 в файл utils.lsp перемещены следующие комментарии и код
    ;;;---------------------------------------------------------------
    ;;; Для функций ActiveX необходимо определить глобальную переменную,
    ;;; "указывающую" на часть пространства модели в активном чертеже. Эта
    ;;; переменная с именем *ModelSpace* создается во время загрузки.
    ;;;---------------------------------------------------------------
    (setq *ModelSpace*
    (vla-get-ModelSpace
         (vla-get-ActiveDocument (vlax-get-acad-object))
    ) ;_ end of vla-get-ModelSpace
    ) ;_ end of setq

    Обратите внимание, что этот фрагмент кода должен находиться в тексте раньше, чем все функции, объявленные defun. Это обеспечивает автоматическое выполнение данного кода VLISP при загрузке файла.

  4. Найдите следующую строку в функции C:GPath:
    5. (setq PolylineName (gp:drawOutline))

    Измените ее следующим образом:

    (setq PolylineName (gp:drawOutline gp_PathData))

    Функция gp:drawOutline ожидает передачи параметра — списка с данными границы полилинии — и это изменение соответствует требуемому.

  5. Добавьте в конец файла gpmain.lsp функцию gp:list->variantArray, описанную в разделе Преобразование списка точек в массив-вариант.

    6. Попробуйте загрузить и выполнить измененную программу. По окончании выполнения программы управление передается из AutoCAD в VLISP, поэтому для просмотра результатов необходимо снова переключиться в окно AutoCAD. Если программа работает правильно, она должна построить границы парковой дорожки. При возникновении ошибок следует отладить код и запустить программу заново.


Итоги занятия 3

Построение контура дорожки
Построение контура дорожки
Итоги занятия 3

На этом занятии были рассмотрены:

  • Служебные функции пользователя, которые можно использовать в различных приложениях.
  • Механизм создания объектов из программы пользователя.
  • Работа с функциями ActiveX.
  • Работа с ассоциативными списками.
  • Реализация функций для построения границ парковой дорожки.

Если что-то в этом занятии осталось непонятным, то, прежде чем переходить к занятию 4, рекомендуется выполнить его еще раз. В этом случае, чтобы начать выполнение упражнений с нужного этапа, следует скопировать код из папки Lesson2. При возникновении затруднений правильный код можно скопировать из папки Tutorial\VisualLISP\Lesson3.


Создание проекта и добавление элементов интерфейса

На этом занятии будут рассмотрены две основные задачи: создание проекта Visual LISP® и добавление в приложение диалогового интерфейса. В ходе занятия файл AutoLISP® (gpmain.lsp) будет разделен на несколько меньших файлов для использования концепции модульности кода.

Начиная с этого занятия, в ходе изложения материала последовательность уже знакомых пользователю операций описывается менее подробно. Для экономии места фрагменты кода сопровождаются минимумом комментариев.

Темы:

  • Разбиение программы на модули
  • Проекты Visual LISP
  • Создание диалогового окна
  • Взаимодействие программы на AutoLISP с диалоговым окном
  • Выбор типа линий для границы дорожки
  • Корректировка
  • Выполнение приложения
  • Итоги занятия 4


Разбиение программы на модули

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Разбиение программы на модули

В результате выполнения третьего занятия файл gpmain.lsp стал достаточно большим. Это не представляет проблем для VLISP; однако работать с программой легче, если разбить ее на несколько файлов, содержащих логически связанные функции. При этом проще выполнить отладку кода. Например, в файле, содержащем 150 функций, крайне сложно найти место, в котором недостает одной скобки.

В учебном пособии файлы модулей будут организованы следующим образом:

Организация файлов учебного пособия
Имя файла | Содержимое
gp-io.lsp | Все функции ввода и вывода (например, для ввода данных пользователем), а также код AutoLISP для диалогового окна, которое будет добавлено в дальнейшем.
utils.lsp | Все общие утилиты, которые могут быть использованы и в других проектах, а также функции, запускаемые при загрузке.
gpdraw.lsp | Все процедуры черчения представляют собой код, создающий объекты AutoCAD.
gpmain.lsp | Основная функция C:GPath.
Для разбиения файла gpmain.lsp на отдельные файлы
  1. Создайте новый файл, затем вырежьте из файла gpmain.lsp и вставьте в новый файл следующие функции:
    • gp:getPointInput
    • gp:getDialogInput

    Сохраните новый файл в рабочей папке под именем gp-io.lsp.

  2. Создайте новый файл, затем вырежьте из файла gpmain.lsp и вставьте в новый файл следующие функции:
    • Degrees->Radians
    • 3Dpoint->2Dpoint
    • gp:list->variantArray

    В начале этого файла вставьте строки кода, загружающего ActiveX (vl-load-com) и присваивающего значение глобальной переменной (*ModelSpace*).

    Сохраните файл под именем utils.lsp.

  3. Создайте новый файл, затем вырежьте из файла gpmain.lsp и вставьте в новый файл следующую функцию:
    • gp:drawOutline

    Сохраните файл под именем gpdraw.lsp.

  4. После вырезания кода сохраните и проверьте файл gpmain.lsp. В нем должна остаться только основная функция C:GPath.

Главное окно VLISP начинает переполняться. Каждое из окон внутри VLISP может быть свернуто, оставаясь, тем не менее, доступным. Для того чтобы развернуть окно, можно либо нажать кнопку "Выбрать окно" на панели инструментов и выбрать нужное окно из списка, либо в меню VLISP нажать "Окно" и выбрать нужное окно.


Проекты Visual LISP

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Проекты Visual LISP

Использование проектов удобный способ управления файлами, из которых образуется приложение. Вместо того чтобы открывать множество LISP-файлов по отдельности, для работы можно открыть только один файл проекта. Для доступа к составным частям открытого проекта достаточно нажать кнопку мыши на имени нужного файла.

Создание проекта VLISP
  1. В меню VLISP выберите "Проект" » "Новый проект".
  2. Сохраните файл в рабочей папке Lesson4 под именем gpath.prj.

    3. После этого VLISP открывает диалоговое окно "Свойства проекта".

  3. Нажмите кнопку "Выбрать/Очистить все" в левой части диалогового окна "Свойства проекта".
  4. Нажмите кнопку со стрелкой, указывающей вправо. Все выделенные файлы добавляются в проект.

    5. В окне "Свойства проекта" в списке слева выводятся имена всех LISP-файлов, расположенных в той же папке, что и файл проекта, но не включенных в этот проект. В списке справа перечисляются имена файлов, входящих в проект. При добавлении к проекту имена выбранных файлов перемещаются из левого списка в правый.

  5. В списке, расположенном в правой части диалогового окна, выделите gpmain и нажмите кнопку "Конец". При этом файл перемещается в конец списка.

    6. VLISP загружает файлы проекта в порядке, в котором они перечислены. Так как подсказка, информирующая пользователей об имени команды построения дорожки, расположена в файле gpmain.lsp, этот файл необходимо переместить в конец списка. Если этот файл будет загружен последним, пользователь увидит подсказку на экране. Файл utils.lsp должен загружаться первым, так как он содержит код инициализации для приложения. Поэтому выберите в списке диалогового окна utils, а затем нажмите кнопку "Начало".

  6. Нажмите "OK".

В главном окне VLISP появляется маленькое окно управления проектом. В этом окне перечисляются файлы, входящие в проект. Для того чтобы открыть файл в текстовом редакторе (если он еще не открыт) и сделать его текущим в проекте, нужно дважды нажать кнопку мыши на его имени.


Создание диалогового окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Создание диалогового окна

Следующая часть данного занятия посвящена созданию диалогового окна для приложения построения парковой дорожки. Для этого потребуется использовать язык управления диалоговыми окнами DCL (dialog control language).

До настоящего времени наша функция gpath воспринимала ввод данных только из командной строки. В месте, где должно вызываться диалоговое окно, была вставлена фиктивная функция (gp:getDialogInput). Теперь настало время создать это диалоговое окно.

Процесс создания диалогового окна состоит из двух этапов:

  • Разработка требований к внешнему виду диалогового окна и необходимому набору элементов управления.
  • Написание кода, управляющего поведением диалогового окна.

Описание и внешний вид диалогового окна задаются в файле с расширением .dcl. В документе AutoLISP Developer's Guide ("Руководство разработчика AutoLISP") DCL описывается в главе 11 “Designing Dialog Boxes” ("Проектирование диалоговых окон"), в главе 12 “Managing Dialog Boxes” ("Управление диалоговыми окнами") и в главе 13 “Programmable Dialog Box Reference” "(Программируемые диалоговые окна: Справочник").

Код программы, включающий функции инициализации рабочих параметров и реакции на действия пользователя, будет добавлен в функцию gp:getDialogInput.

Темы:

  • Описание диалогового окна с помощью DCL
  • Сохранение DCL-файла
  • Предварительный просмотр диалогового окна


Описание диалогового окна с помощью DCL

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Описание диалогового окна с помощью DCL

Сначала определим, какое окно нужно создать.

Диалоговое окно должно содержать следующие элементы:

  • Два переключателя.

    • Один переключатель управляет типом полилинии, образующей границу; другой задает метод создания плиток (ActiveX, entmake или command). Каждый из переключателей в определенный момент времени может быть установлен только в одно из положений.

  • Текстовые поля для задания радиуса плиток и расстояния между ними.
  • Стандартные кнопки "OK" и "Cancel".

Компоненты диалогового окна называются его элементами. Полное описание диалогового окна в файле DCL вначале может казаться трудной задачей. Поэтому нужно составить общую схему того, что необходимо включить в диалоговое окно, а затем разбить ее на несколько частей и описать каждую часть отдельно.

Описание диалогового окна
  1. Откройте новый файл в текстовом редакторе VLISP.
  2. В новом файле введите следующее выражение:
    3. label = "Garden Path Tile Specifications";

    Это выражение DCL описывает заголовок диалогового окна.

  3. Опишите с помощью следующего кода положения переключателя, используемые для задания типа полилинии:
    4. : boxed_radio_column { // определяет положение кнопки-переключателя
      label = "Тип контура полилинии";
      : radio_button { // определяет компактную кнопку-переключатель
        label = "&Компактный";
        key = "gp_lw";
        value = "1";
       }
    : radio_button { // определяет кнопку-переключатель полилинии старого формата
      label = "&Старый формат";
      key = "gp_hw";
     }
    }

    DCL-директива boxed_radio_column описывает границу группы переключателя и позволяет указать его имя. Внутри границы отдельные положения переключателя задаются директивами radio_button. Для каждого положения переключателя необходимо задать имя и ключ. Ключ - это имя, по которому идентифицируется положение переключателя внутри AutoLISP .

    Обратите внимание, что переключателю “lightweight” присвоено значение 1. Значение 1 (это строка, а не целое число) указывает, что данное положение должно быть активным по умолчанию. Другими словами, при появлении диалогового окна на экране в переключателе устанавливается выбранным именно этот вариант. Следует также запомнить, что в DCL-файлах символами комментариев являются две косых черты, а не точка с запятой, как в программах AutoLISP.

  4. Опишите с помощью следующего кода переключатель, используемый для задания метода создания объектов:
    5. : boxed_radio_column { // определяет положение кнопки-переключателя
      label = "Метод создания плиток";
      : radio_button { // определяет кнопку-переключатель ActiveX
        label = "&ActiveX Automation";
        key = "gp_actx";
        value = "1";
       }
    : radio_button { // определяет кнопку-переключатель (функции entmake)
      label = "&Entmake";
      key = "gp_emake";
     }
    : radio_button { // определяет кнопку-переключатель (команды)
      label = "&Команда";
      key = "gp_cmd";
     }
    }
  5. Для описания текстовых полей, в которых будет задаваться размер плиток и расстояния между ними, добавьте следующий код:
    6. : edit_box { // определяет окно редактирования "Радиус плитки"
      label = "&Радиус плитки";
      key = "gp_trad";
      edit_width = 6;
    }
    : edit_box { // определяет окно редактирования "Расстояние между плитками"
      label = "Расстояние ме&жду плитками";
      key = "gp_spac";
      edit_width = 6;
    }

    Обратим внимание, что текстовым полям не присваивается никакого исходного значения. Значение по умолчанию для каждого текстового поля задаются из AutoLISP-программы.

  6. Добавьте следующий код для кнопок "OK" и "Cancel":
    7. : row { // определяет кнопки "OK"/"Отмена"
      : spacer { width = 1; }
      : button { // определяет кнопку "OK"
        label = "OK";
        is_default = true;
        key = "accept";
        width = 8;
        fixed_width = true;
      }
      : button { // определяет кнопку "Отмена"
        label = "Cancel";
        is_cancel = true;
        key = "cancel";
        width = 8;
        fixed_width = true;
      }
      : spacer { width = 1;}
    }

    Обе кнопки описаны внутри одного ряда, поэтому они устанавливаются по одной горизонтальной линии.

  7. Установите курсор в верхней части окна текстового редактора и вставьте первой строкой следующее выражение:
    8. gp_mainDialog : dialog {
  8. Директива dialog требует закрывающей фигурной скобки. Поэтому поместите курсор в конец файла и вставьте закрывающую фигурную скобку последней строкой DCL-кода:
    9. }


Сохранение DCL-файла

Перед сохранением файла с DCL следует учесть, что программа AutoCAD® должна находить DCL-файл во время работы. Поэтому файл должен находиться по одному из путей доступа к вспомогательным файлам AutoCAD. Чтобы проверить местоположение, выберите в меню AutoCAD "Сервис" » "Параметры" и проверьте, какие папки указаны в узле "Путь поиска вспомогательных файлов" на вкладке "Файлы").

Сохраните файл в папке Support программы AutoCAD.

Сохранение DCL-файла
  1. В меню VLISP выберите "Файл" » "Сохранить как".
  2. Из раскрывающегося списка "Тип файла" диалогового окна "Сохранить как" выберите "Исходные DCL-файлы".
  3. Измените текущий путь на папка <AutoCAD>\Support.
  4. Введите имя файла gpdialog.dcl.
  5. Нажмите кнопку "Сохранить".

Обратите внимание, как после сохранения файла VLISP выделяет его элементы цветом. VLISP распознает директивы языка DCL и старается таким образом облегчить работу с ними для пользователя.


Предварительный просмотр диалогового окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Предварительный просмотр диалогового окна

VLISP предоставляет функцию предварительного просмотра для проверки результатов программирования на DCL.

Для предварительного просмотра диалогового окна, описанного с помощью DCL
  1. В меню VLISP выберите "Сервис" » "Инструменты интерфейса" » "Просмотр DCL из редактора".
  2. Нажмите "OK" в ответ на запрос ввести имя диалогового окна.

    3. В данном случае в DCL-файле описывается только одно диалоговое окно. Однако, для более сложных приложений DCL-файлы могут содержать описания нескольких диалоговых окон. В этом случае пришлось бы выбирать, какое из них нужно просмотреть.

  3. Если диалоговое окно выглядит правильно, нажмите любую кнопку для выхода из него.

Для вывода диалогового окна VLISP передает управление AutoCAD. При нахождении ошибок AutoCAD выдает одно или несколько соответствующих сообщений.

Если программой AutoCAD были обнаружены ошибки в DCL и их не удается исправить, следует скопировать файл gpdialog.dcl из папки Tutorial\VisualLISP\Lesson4 в папку Support.


Взаимодействие программы на AutoLISP с диалоговым окном

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Взаимодействие программы на AutoLISP с диалоговым окном

Теперь надо сделать так, чтобы программа взаимодействовала с диалоговым окном. Для этого модифицируем фиктивную функцию gp:getDialogInput. Функция хранится в файле gp-io.lsp, который ранее был извлечен из файла gpmain.lsp.

Поначалу разработка интерфейса программы с диалоговым окном может вызвать у пользователя затруднения. Проектируя интерфейс, необходимо ответить на следующие вопросы:

  • Нужно ли задавать для элементов окна значения по умолчанию?
  • Что происходит при нажатии на кнопку или вводе значения?
  • Что происходит при нажатии на кнопку "Cancel"?
  • Что должно произойти, если файл диалогового окна (.dcl) отсутствует?

Темы:

  • Задание значений для диалогового окна
  • Загрузка файла диалогового окна
  • Загрузка нужного диалогового окна в память
  • Инициализация значений по умолчанию для диалогового окна
  • Назначение действий элементам окна
  • Запуск диалогового окна
  • Выгрузка диалогового окна
  • Дальнейшие действия
  • Компоновка кода
  • Обновление фиктивной функции


Задание значений для диалогового окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Задание значений для диалогового окна

При выполнении законченного приложения построения парковой дорожки видно, что в диалоговом окне по умолчанию выбирается метод ActiveX для создания объектов и тип "Lightweight" (компактные) для полилиний. Что же касается значений параметров плиток, предлагаемых по умолчанию, то они зависят от ширины дорожки. Следующий фрагмент кода показывает, как устанавливаются значения по умолчанию для диалогового окна:

(setq objectCreateMethod "ACTIVEX"
        plineStyle "LIGHT"
        tilerad (/ pathWidth 15.0)
        tilespace (/ tilerad 5.0)
        dialogLoaded T
        dialogShow T
  ) ;_ end of setq

Назначение переменных dialogLoaded и dialogShow пока не будем объяснять. Это станет ясно после рассмотрения следующих двух разделов.


Загрузка файла диалогового окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Загрузка файла диалогового окна

В программе сначала необходимо загрузить файл DCL с помощью функции load_dialog. Эта функция производит поиск файла диалогового окна в папках доступа к вспомогательным файлам AutoCAD, если путь для него не задан явно при вызове функции.

Каждому вызову функции load_dialog должен соответствовать вызов функции unload_dialog, который должен выполняться ниже в тексте программы. Скоро мы это увидим. Сейчас же рассмотрим, как осуществляется загрузка диалогового окна:

;; Load the dialog box. Set up error checking to make sure
;; the dialog file is loaded before continuing
(if (= -1 (setq dcl_id (load_dialog "gpdialog.dcl")))
    (progn
      ;; Ошибка - вывести сообщение и установить
      ;; флаг dialogLoaded в nil
      (princ "\nНевозможно загрузить gpdialog.dcl")
      (setq dialogLoaded nil)
    ) ;_ end of progn
) ;_ end of if

Переменная dialogLoaded указывает, что диалоговое окно было успешно загружено. В коде программы, где задавались исходные значения для диалогового окна, переменной dialogLoaded было присвоено начальное значение T. Во фрагменте, описанном выше, переменной dialogLoaded присваивается значение nil в случае возникновения проблем с загрузкой окна.


Загрузка нужного диалогового окна в память

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Загрузка нужного диалогового окна в память

Ранее было отмечено, что отдельный DCL-файл может содержать описания нескольких диалоговых окон. В таком случае необходимо указать, какое именно из диалоговых окон требуется. Это иллюстрируется в следующем фрагменте кода:

(if (and dialogLoaded
        (not (new_dialog "gp_mainDialog" dcl_id))
     ) ;_ end of and
    (progn
      ;; Ошибка...
      (princ "\nНевозможно открыть диалоговое окно gp_mainDialog")
      (setq dialogShow nil)
    ) ;_ end of progn
) ;_ end of if

Обратим внимание на то, как используется функция and для проверки, загружено ли диалоговое окно и был ли успешно выполнен вызов new_dialog. При наличии внутри одной функции and нескольких выражений, вычисление прекращается после того, как результатом какого-либо из последовательных выражений станет nil. Например, если переменной dialogLoaded присвоено значение nil (что означает, что при выполнении функции загрузки произошла ошибка), VLISP не пытается выполнить функцию new_dialog.

Следует отметить, что в коде также учитывается возможность наличия неверных элементов в файле DCL. В этом случае переменной dialogShow присваивается значение nil.

Функция new_dialog загружает диалоговое окно в память, но не выводит его на монитор. Для вывода диалогового окна на экран используется функция start_dialog. Остальные этапы инициализации диалогового окна (присвоение значений элементам окна, создание изображений и списков, а также задание действий для элементов окна), должны производится после вызова new_dialog, но перед вызовом start_dialog.


Инициализация значений по умолчанию для диалогового окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Инициализация значений по умолчанию для диалогового окна

Если загрузка диалогового окна прошла успешно, можно приступить к заданию исходных значений. При успешной загрузке переменным dialogLoadedиdialogShow присваивается значение T (истина).

Теперь установим начальные значения для радиуса плиток и расстояния между ними. Функция set_tile присваивает значение элементу управления диалогового окна. В текстовом поле используются строковые переменные (а не числа), поэтому для преобразования значения размера плиток в строки десятичного формата с точностью до двух знаков используется функция rtos (преобразование вещественнных чисел в строки). Преобразование осуществляется с помощью следующего кода:

(if (and dialogLoaded dialogShow)
    (progn
      ;; Задание исходного состояния плиток
      (set_tile "gp_trad" (rtos tileRad 2 2))
      (set_tile "gp_spac" (rtos tileSpace 2 2))


Назначение действий элементам окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Назначение действий элементам окна

В файле DCL описывается только статическая внешняя форма диалогового окна. Теперь свяжем это описание с кодом AutoLISP, "оживляющим" окно. Используем для этого функцию action_tile, как показано в следующем фрагменте:

;; Назначение действий (вызываемые функции) кнопкам диалогового окна
(action_tile
  "gp_lw"
  "(setq plineStyle \"Источник освещения\")"
)
(action_tile
  "gp_hw"
  "(setq plineStyle \"Плиния\")"
)
(action_tile
  "gp_actx"
  "(setq objectCreateMethod \"ActiveX\")"
)
(action_tile
  "gp_emake"
  "(setq objectCreateMethod \"Entmake\")"
)
(action_tile
  "gp_cmd"
  "(setq objectCreateMethod \"Команда\")"
)
(action_tile "отмена" "(done_dialog) (setq UserClick nil)")
(action_tile
  "accept"
  (strcat "(progn (setq tileRad (atof (get_tile \"gp_trad\")))"
       "(setq tileSpace (atof (get_tile \"gp_spac\")))"
       "(done_dialog) (setq UserClick T))"
  )
)

Обратим внимание на кавычки вокруг кода AutoLISP. Функция AutoLISP action_tile передает плитке информацию о том, что следует запомнить эту строку, а затем передать ее обратно, когда пользователь выберет эту плитку.Строка (текст, заключенный в двойные кавычки) является бездействующей, пока пользователь не выберет плитку. Именно тогда элемент окна передает строку в AutoCAD, где строка преобразуется в рабочий код AutoLISP и выполняется.

Например, рассмотрим следующее выражение action_tile, которое связано с положением переключателя для компактной полилинии:

(action_tile
   "gp_lw"
   "(setq plineStyle \"Источник освещения\")" "
)

Код присваивает положению переключателя строку "(setq plineStyle \"Источник освещения\")". Когда пользователь переводит переключатель в данное положение, строка передается в AutoCAD и преобразуется непосредственно в следующее выражение AutoLISP:

(setq plineStyle "Источник освещения")

Рассмотрим еще один фрагмент кода. Следующее выражение action_tile присвоено кнопке "OK":

(action_tile
    "accept"
    (strcat "(progn (setq tileRad (atof (get_tile \"gp_trad\")))"
      "(setq tileSpace (atof (get_tile \"gp_spac\")))"
      "(done_dialog) (setq UserClick T))"
)

При нажатии кнопки "OK" длинная строка, назначенная кнопке, передается в AutoCAD и преобразуется в следующий код на AutoLISP:

(progn
   (setq tileRad (atof (get_tile "gp_trad")))
   (setq tileSpace (atof (get_tile "gp_spac")))
   (done_dialog)
   (setq UserClick T)
)

Код выполняет несколько действий. Он извлекает текущие значения элементов диалогового окна с ключевыми значениями gp_trad (радиус плитки) и gp_spac(расстояние между плитками). Затем с помощью функции atof строка чисел преобразуется в вещественное число. Функция done_dialog прерывает работу диалогового окна, и переменной UserClick присваивается значение T (истина).

Кнопкам назначены действия. Следующий этап заставить их работать.


Запуск диалогового окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Запуск диалогового окна

Функция start_dialog выводит диалоговое окно на экран и воспринимает пользовательский ввод. Эта функция не требует аргументов.

(start_dialog)

Управление передается пользователю при вызове функции start_dialog. Он может изменять настройки в диалоговом окне до тех пор, пока не будет нажата одна из кнопок "OK" или "Cancel".


Выгрузка диалогового окна

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Выгрузка диалогового окна

При нажатии кнопок "OK" или "Cancel" диалоговое окно должно выгружаться. Для этого используется функция unload_dialog (такая же простая в применении, как и start_dialog).

(unload_dialog dcl_id)


Дальнейшие действия

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Дальнейшие действия

Необходимо, чтобы после нажатия пользователем кнопки "OK" программа создавала список значений, заданных в диалоговом окне. Этот список функция gp:getDialogInput должна возвращать вызвавшей ее функции. Если пользователь нажал кнопку "Отмена", функция возвращает nil:

(if UserClick ; Пользователь нажал "Ok"
  ;; Создание результирующих данных
  (progn
    (setq Result (list
        (cons 42 tileRad)
        (cons 43 TileSpace)
        (cons 3 objectCreateMethod)
        (cons 4 plineStyle)
        )
     )
  )
)


Компоновка кода

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Компоновка кода

Функция gp:getDialogInput состоит из рассмотренных примеров и включает в себя еще несколько строк.

Для объединения кода функции gp:getDialogInput
  1. Откройте файл gp-io.lsp в окне текстового редактора VLISP.
  2. Удалите имеющийся код функции gp:getDialogInput (выражение defun gp:getDialogInput и все, что идет за ним).
  3. В качестве первой строки функции gp:getDialogInput введите следующее выражение defun:
    4. (defun gp:getDialogInput (pathWidth / dcl_id objectCreateMethod
                   plineStyle tilerad tilespace result UserClick
                   dialogLoaded dialogShow)

    Функция ожидает задания одного аргумента (pathwidth) и имеет ряд локальных переменных.

  4. Вслед за кодом, добавленным в пункте 3, введите код примеров из следующих разделов данного занятия:
    • Задание значений для диалогового окна
    • Загрузка файла диалогового окна
    • Загрузка нужного диалогового окна в память
    • Инициализация значений по умолчанию для диалогового окна
    • Назначение действий элементам окна

    Примечание Из раздела выберите только первый пример кода Назначение действий элементам окна. Не вводите следующие за ним фрагменты в пояснениях. Эти фрагменты просто повторяют участки исходного примера.

    • Запуск диалогового окна
    • Выгрузка диалогового окна
    • Дальнейшие действия
  5. После последней строки кода добавьте следующее:
    6.    )
     )
      Result;
    ) ;_ end of defun
  6. Для форматирования кода выберите в меню VLISP "Сервис" » "Форматировать код в редакторе".


Обновление фиктивной функции

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Обновление фиктивной функции

Функция gp:getDialogInput была изменена. При изменении фиктивной функции всегда следует проверять две вещи:

  • Изменилось ли выражение defun? Необходимо ли функции то же число аргументов, что и раньше?
  • Изменился ли результат, возвращаемый функцией?

В случае функции gp:getDialogInput ответ на оба вопроса положителен. Функция теперь принимает параметр ширину дорожки (для определения значений по умолчанию для размера плиток и расстояния между ними). Вместо символа T, который возвращала раньше фиктивная функция, теперь результатом выполнения функции gp:getDialogInput является ассоциативный список, состоящий из четырех значений.

Оба изменения влияют как на код, вызывающий функцию, так и на код, обрабатывающий полученные функцией результаты. Замените предыдущую версию функции C:GPath в файле gpmain.lsp следующим кодом:

(defun C:GPath (/ gp_PathData gp_dialogResults)
  ;; Запрос на ввод: путь к местоположению и
  ;; направления, затем параметров пути.  Продолжить только при
  ;; действительных входных данных.  Store the data in gp_PathData.
  (if (setq gp_PathData (gp:getPointInput))
    (if (setq gp_dialogResults (gp:getDialogInput (cdr(assoc 40
                               gp_PathData))))
      (progn
       ;; Теперь результаты gp:getPointInput следует присоединить к
       ;; информации, добавленной функцией gp:getDialogInput.
       (setq gp_PathData (append gp_PathData gp_DialogResults))
       ;; В этом месте получены действительные входные данные от пользователя.
       ;; Начертить контур с сохранением результирующей полилинии
       ;; "указатель" в переменной вызывает PolylineName.
       (setq PolylineName (gp:drawOutline gp_PathData))
      ) ;_ end of progn
     (princ "\nОтмена функции.")
     ) ;_ end of if
    (princ "\nНедостаточно данных для создания границы.")
  ) ;_ end of if
  (princ) ; тихий выход
) ;_ end of defun

Обратите внимание на строки в измененной версии функции C:GPath, выделенные полужирным шрифтом. Для того чтобы программа работала правильно, внесено два основных изменения:

  • При вызове функции gp:getDialogInput ей передается ширина дорожки. Для этого из ассоциативного списка gp_PathData извлекается значение, которому присвоен код 40.
  • Ассоциативный список, возвращаемый функцией gp:getPointInput, записывается в переменную gp_dialogResults. Значение этой переменной, если оно имеется, добавляется к списку значений ассоциативного списка, уже хранящихся в gp_PathData.

Дополнительные изменения вызваны удалением теперь уже ненужных элементов фиктивной функции. Наиболее простой способ произвести их скопировать код из электронного учебного пособия и вставить его в файл.


Выбор типа линий для границы дорожки

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Выбор типа линий для границы дорожки

При построении границы парковой дорожки приложение должно позволять пользователю выбирать между компактной и обычной полилинией. В первой версии функции gp:drawOutline граница всегда представляла собой компактную полилинию. Теперь, когда готово диалоговое окно, можно создать процедуру и для построения обычной полилинии. Для этого функция gp:drawOutline должна перед рисованием границы определять, какой тип полилинии необходимо использовать.

Необходимые изменения функции gp:drawOutline включены в следующий фрагмент кода. Изменения в файле gpdraw.lsp обозначены полужирным шрифтом.

(setq PathAngle (cdr (assoc 50 BoundaryData))
    Width (cdr (assoc 40 BoundaryData))
    HalfWidth (/ Width 2.00)
    StartPt (cdr (assoc 10 BoundaryData))
    PathLength (cdr (assoc 41 BoundaryData))
    angp90 (+ PathAngle (Degrees->Radians 90))
    angm90 (- PathAngle (Degrees->Radians 90))
    p1 (polar StartPt angm90 HalfWidth)
    p2 (polar p1 PathAngle PathLength)
    p3 (polar p2 angp90 Width)
    p4 (polar p3 (+ PathAngle (Degrees->Radians 180))
               PathLength)
    poly2Dpoints (apply 'append
                (mapcar '3dPoint->2dPoint (list p1 p2 p3 p4))
             )
    poly3Dpoints (mapcar 'float (append p1 p2 p3 p4))
    ;; получить стиль полилинии
    plineStyle (strcase (cdr (assoc 4 BoundaryData)))
) ;_ end of setq
;; Добавление полилинии к пространству модели автоматически с использованием ActiveX
(setq pline (if (= plineStyle "LIGHT")
     ;; создание компактной полилинии
     (vla-addLightweightPolyline
          *ModelSpace*; Глобальное определение пространства модели
          (gp:list->variantArray poly2Dpoints) ;преобразование данных
      ) ;_ end of vla-addLightweightPolyline
     ;; или создание полилинии в старом стиле
     (vla-addPolyline
          *ModelSpace*
          (gp:list->variantArray poly3Dpoints) ;преобразование данных
     ) ;_ end of vla-addPolyline
    ) ;_ end of if
) ;_ end of setq

При вводе кода вручную следует быть очень внимательным, так как здесь приходится не только добавлять код, но также удалять и переставлять строки. Рекомендуется просто скопировать код из электронного учебного пособия и вставить его в файл.


Корректировка

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Корректировка

Из функции C:GPath (файл gpmain.lsp) нужно удалить следующий фрагмент кода:

(princ "\nВозврат функции gp:drawOutline <")
(princ PolylineName)
(princ ">")
(Alert "Поздравляем! Программа создана!")

Так как функция gp:drawOutline теперь готова и полностью работоспособна, этот фрагмент, использовавшийся в качестве программной "заглушки", больше не нужен.


Выполнение приложения

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Выполнение приложения

Перед запуском программы все измененные файлы следует сохранить. Для сохранения всех открытых файлов выберите в меню VLISP "Файл" » "Сохранить все" или используйте комбинацию клавиш ALT + SHIFT + S.

Теперь необходимо перезагрузить все файлы в VLISP.

Загрузка и выполнение файлов приложения
  1. Если файл проекта, созданный ранее в этом занятии, еще не открыт, выберите в меню VLISP "Проект" » "Открыть проект", затем введите имя файла проекта gpath без расширения .prj. Если VLISP не находит файл проекта, нажмите кнопку "Обзор" и разыщите файл в диалоговом окне "Открытие проекта". Нажмите кнопку "Открыть".
  2. Нажмите кнопку "Загрузка исходных файлов" в окне проекта.
  3. В окне консоли VLISP введите команду (C:GPath) для запуска программы. При необходимости можно воспользоваться для отладки программы инструментами, описанными на занятиях 2 и 3. Следует помнить, что при возникновении затруднений правильный код можно скопировать из папки Tutorial\VisualLISP\Lesson4. Желательно попробовать построить границу дорожки двумя способами:

в виде компактной и в виде обычной полилинии. После построения дорожки воспользуйтесь командой список AutoCAD для проверки типа построенных объектов дорожки.


Итоги занятия 4

Создание проекта и добавление элементов интерфейса
Создание проекта и добавление элементов интерфейса
Итоги занятия 4

На этом занятии были рассмотрены:

  • Разбиение кода на модули, хранящиеся в четырех отдельных файлах.
  • Организация модулей кода в проект VLISP.
  • Описание диалогового окна с помощью языка DCL (Dialog Control Language).
  • Создание кода AutoLISP для обработки вводимых в диалоговом окне данных.
  • Доработка кода с целью предоставления пользователю возможности выбора типа линий границ.

Теперь программа умеет строить границу дорожки. На следующем занятии мы разместим на дорожке плитки. В ходе выполнения упражнений мы познакомимся еще с несколькими инструментами разработки VLISP.


Создание плиток

К концу этого занятия программа должна отвечать основным требованиям, сформулированным на занятии 1. В нее будут добавлены функции для заполнения дорожки плитками. Пользователю будет предоставлена возможность выбора между тремя способами создания плиток. Кроме того, будут рассмотрены использование сочетания клавиш для ускорения ввода и новые инструменты редактирования.

Темы:

  • Продолжение знакомства со средствами редактирования Visual LISP
  • Создание плиток на парковой дорожке
  • Проверка работы кода программы
  • Итоги занятия 5


Продолжение знакомства со средствами редактирования Visual LISP

Создание плиток
Создание плиток
Продолжение знакомства со средствами редактирования Visual LISP

Если файл еще не открыт, откройте копию файла gpdraw.lsp в текстовом редакторе в Visual LISP®. Код любой AutoLISP-программы обладает некоторыми характерными особенностями. Во-первых, он содержит множество вложенных друг в друга скобок. Во-вторых, существует множество вызовов функций, и некоторые из этих функций имеют очень длинные имена (например, vla-addLightweightPolyline). Инструменты редактирования VLISP обеспечивают наиболее эффективную работу с этими типичными для всех программ AutoLISP® элементами кода.

Темы:

  • Сопоставление скобок
  • Автоматическое дописывание слов
  • Дописывание словом из списка
  • Получение справочной информации о функции


Сопоставление скобок

Создание плиток
Создание плиток
Сопоставление скобок

VLISP обеспечивает проверку соответствия открывающих и закрывающих скобок.

Сопоставления открывающей и соответствующей ей закрывающей скобок
  1. Поместите курсор перед открывающей скобкой функции setq.
  2. Нажмите CTRL + SHIFT + ]. Можно также дважды нажать кнопку мыши на нужной скобке.

VLISP находит закрывающую скобку, которая соответствует выбранной открывающей скобке, и выделяет код между ними. Это не только обеспечивает соответствие скобок, но и позволяет избежать ошибок при копировании выделенного текста. Это, например, могло пригодиться при модификации функции в конце занятия 4.

Для чего еще можно использовать сопоставление скобок? С его помощью можно скопировать фрагмент кода в окно консоли VLISP для проверки его работы. Или, пусть, например, требуется заменить 50 строк имеющегося кода на 3 строки более удачного кода. Старый код можно быстро выделить, используя функцию сопоставления скобок, а затем удалить одним нажатием клавиши. Пользователю значительно удобнее возложить на VLISP поиск блоков кода программы, нежели самому проверять каждую закрывающую скобку.

Для сопоставления скобок в обратном направлении также используется специальное сочетание клавиш. В этом случае, установив курсор после закрывающей скобки, необходимо дважды нажать кнопку мыши или комбинацию клавиш CTRL+SHIFT+[. VLISP находит соответствующую открывающую скобку и выделяет все заключенное в скобки выражение.

Обе команды можно выбрать с помощью меню VLISP "Правка" » "Сопоставление скобок".


Автоматическое дописывание слов

Создание плиток
Создание плиток
Автоматическое дописывание слов

Пусть необходимо добавить к программе следующий фрагмент кода:

ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData)))
(if (equal ObjectCreationStyle "COMMAND")
 (progn
  (setq firstCenterPt(polar rowCenterPt (Degrees->Radians 45) distanceOnPath))
    (gp:Create_activeX_Circle)
  )
)

Сейчас можно не тратить время на изучение содержимого этого кода. Он используется только в качестве примера, иллюстрируя способы обращения с длинными именами переменных и функций.

VLISP может автоматически дописывать длинные слова по их начальным буквам.

Для использования функции дописывания слов в Visual LISP
  1. Переместите курсор в конец файла gpdraw.lsp и введите следующий код:
    2. ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData)))
       (if (equal Ob
  2. Нажмите CTRL + ПРОБЕЛ.

    3. VLISP находит ближайшее соответствие для двух введенных последними букв и автоматически дописывает слово, позволяя не тратить время на ввод оставшихся 17 букв.

  3. Допишите строку до конца:
    4. (if (equal ObjectCreationStyle "COMMAND")
  4. Добавьте следующие строки:
    5.  (progn
      (setq firstCenterPt(p
  5. Нажмите CTRL + ПРОБЕЛ.

    6. VLISP подставляет последнее слово, начинающееся на “p”, то есть progn. Однако нам нужно слово polar. Если продолжать нажимать CTRL + ПРОБЕЛ, VLISP начнет циклически перебирать все подходящие слова, использованные в коде. И, в конце концов, будет найдено слово polar.

  6. Удалите только что введенный код. Он был нужен только в качестве примера.

    7. Функция дописывания слов доступна также и через меню VLISP "Поиск".


Дописывание словом из списка

Создание плиток
Создание плиток
Дописывание словом из списка

Пользователям, работавшим с AutoLISP ранее, вероятно, уже приходилось вводить выражения, аналогичные следующему:

(setq myEnt (ssname mySelectionSet ssIndex))

Часто бывает трудно отследить все функции набора объектов: ssname, ssget, sslength и т.д. Здесь может пригодиться средство VLISP "Дописать словом из списка".

Для использования функции дописывания словом из списка в Visual LISP
  1. Переместите курсор в конец файла gpdraw.lsp и введите на пустой строке следующее:
    2. (setq myEnt (ent
  2. Нажмите CTRL + SHIFT + ПРОБЕЛ.

    3. VLISP выводит список всех символов AutoLISP, начинающихся с ent.

    С помощью клавиш курсора (стрелок "вверх" и "вниз") можно перемещаться по списку. Выберите ENTGET и нажмите ENTER.

    VLISP заменяет введенный фрагмент слова ent на ENTGET.

  3. Удалите код.


Получение справочной информации о функции

Создание плиток
Создание плиток
Получение справочной информации о функции

Код, создающий на чертеже компактную полилинию, вызывает функцию vla-addLightweightPolyline. Помимо того, что эта функция имеет очень длинное имя, есть еще несколько функций, предназначенных для создания объектов, имена которых также начинаются с vla-add. Вместо того чтобы искать каждый раз при написании программы имя функции в справочнике, можно воспользоваться электронной справкой по VLISP.

Получение справки о функции
  1. Введите на новой строке следующее:
    2. (vla-add
  2. Нажмите CTRL + SHIFT + ПРОБЕЛ.
  3. Найдите в списке функцию vla-addLightweightPolyline.
  4. Дважды нажмите кнопку мыши на функции vla-addLightweightPolyline.

    5. VLISP выводит для выбранной функции диалоговое окно "Инспектор символов".

  5. В диалоговом окне "Инспектор символов" нажмите кнопку "Справка". (Для функций ActiveX® откроется документ ActiveX and VBA Reference ("Справочник по ActiveX and VBA").
  6. Удалите изменения, внесенные в файл gpdraw.lsp. Они были нужны только в качестве примера. Также закройте диалоговые окна "Инспектор символов" и "Справочный список".


Создание плиток на парковой дорожке

Создание плиток
Создание плиток
Создание плиток на парковой дорожке

У нас уже имеется граница дорожки, которую теперь нужно заполнить плитками. Для решения данной задачи потребуется немного подумать и вспомнить кое-что из геометрии.

Темы:

  • Логика построения
  • Геометрические величины
  • Построение рядов плиток
  • Построение плиток в ряду
  • Изучение кода


Логика построения

Создание плиток
Создание плиток
Логика построения

Нужно подумать о том, как расположить плитки на дорожке. Если это простая прямоугольная решетка из плиток, то для заполнения плиткой можно использовать команду AutoCAD® МАССИВ. Однако в реальной жизни плитки на дорожке обычно располагают в шахматном порядке.

Плитки образуют периодический узор. Подумаем, как бы мы выкладывали плитки на настоящей дорожке. Вероятно, удобнее начать с одного конца и выкладывать ряды плитки до тех пор, пока не будет заполнено все пространство.

Словесное описание алгоритма выглядит следующим образом:

В начальной точке дорожки
Определить смещение начального ряда плиток относительно центральной линии дорожки (центр средней плитки ряда либо
совпадает с центральной линии дорожки, либо смещен относительно этой линии на половину "шага плитки").
Пока заполненное расстояние на дорожке меньше длины, которую нужно
заполнить,
  Построить ряд плиток.
  Сместить начальную точку для следующего ряда (увеличить на один "шаг плитки").
  Добавить расстояние, занимаемое новым рядом плиток, к уже
  заполненному пространству.
  Изменить смещение ряда относительно центральной линии дорожки (если оно было равным 0,
  сместить на половину "шага плитки", и наоборот).
  Вернуться в начало цикла.


Геометрические величины

Создание плиток
Создание плиток
Геометрические величины

Для построения парковой дорожки нужно знать всего несколько размеров. Полуширина - это половина ширины дорожки. Процедура для получения этого значения и сохранения его в ассоциативном списке уже создана.

Шаг плитки равен сумме диаметра плитки и расстояния между плитками. Размеры задаются пользователем.

Шаг между рядами вычисляется по несколько более сложной тригонометрической формуле: промежуток между рядами

Шаг между рядами = (Диаметр плитки + Расстояние между плитками) * (синус угла
               60 градусов)


Построение рядов плиток

Создание плиток
Создание плиток
Построение рядов плиток

Попробуем разобраться со следующей функцией. Для этого сравним ее с описанным алгоритмом и найдем упомянутые выше геометрические величины. Здесь может встретиться несколько новых функций AutoLISP. Более подробную информацию об этих функциях см. в электронном документе AutoLISP Reference ("Справочник по AutoLISP"). Теперь ознакомимся с кодом (но пока не надо добавлять его в файл).

(defun gp:Calculate-and-Draw-Tiles (BoundaryData / PathLength
           TileSpace TileRadius SpaceFilled SpaceToFill
           RowSpacing offsetFromCenter
           rowStartPoint pathWidth pathAngle
           ObjectCreationStyle TileList)
  (setq PathLength (cdr (assoc 41 BoundaryData))
        TileSpace (cdr (assoc 43 BoundaryData))
        TileRadius (cdr (assoc 42 BoundaryData))
        SpaceToFill (- PathLength TileRadius)
        RowSpacing (* (+ TileSpace (* TileRadius 2.0))
                         (sin (Degrees->Radians 60))
                      ) ;_ end of *
        SpaceFilled RowSpacing
        offsetFromCenter 0.0
        offsetDistance (/ (+ (* TileRadius 2.0) TileSpace) 2.0)
        rowStartPoint (cdr (assoc 10 BoundaryData))
        pathWidth (cdr (assoc 40 BoundaryData))
        pathAngle (cdr (assoc 50 BoundaryData))
        ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData)))
  ) ;_ end of setq
  ;; Компенсация первого вызова фукнции gp:calculate-Draw-tile Row
  ;; в следующем цикле.
  (setq rowStartPoint
       (polar rowStartPoint
            (+ pathAngle pi)
            (/ TileRadius 2.0)
       ) ;_ end of polar
  ) ;_ end of setq
  ;; Draw each row of tiles.
  (while (<= SpaceFilled SpaceToFill)
    ;; Получение созданных плиток, добавление их к списку.
    (setq tileList (append tileList
              (gp:calculate-Draw-TileRow
                (setq rowStartPoint
                     (polar rowStartPoint
                            pathAngle
                            RowSpacing
                     ) ;_ end of polar
                ) ;_ end of setq
                TileRadius
                TileSpace
                pathWidth
                pathAngle
                offsetFromCenter
                ObjectCreationStyle
              ) ;_ end of gp:calculate-Draw-TileRow
            ) ;_ end of append
     ;; Расчет расстояния вдоль дорожки для следующего ряда.
     SpaceFilled (+ SpaceFilled RowSpacing)
     ;; Чередование нулевого и положительного смещения
     ;; (вызывает чередование рядов).
     offsetFromCenter
           (if (= offsetFromCenter 0.0)
                  offsetDistance
                  0.0
           ) ;_ end of if
    ) ;_ end of setq
  );_ end of while
  ;; Возврат списка созданных плиток.
  tileList
) ;_ end of defun

Некоторые участки кода требуют дополнительных пояснений.

Следующий фрагмент располагается непосредственно перед началом цикла while:

;; Компенсация для первой точки!!
(setq rowStartPoint(polar rowStartPoint
(+ pathAngle pi)(/ TileRadius 2.0)))

Здесь необходимо пояснить следующее:

  • Переменная rowStartPoint создается функцией gp:Calculate-and-Draw-Tiles и назначается точке, указанной пользователем в качестве начальной точки дорожки.
  • Самый первый аргумент, передаваемый функции gp:calculate-Draw-TileRow, выглядит так:

    • (setq rowStartPoint(polar rowStartPoint pathAngle RowSpacing))

    Другой способ этого утверждения: при вызове функции gp:calculate-Draw-TileRow точка rowStartPoint переносится на расстояние RowSpacing относительно текущего положения rowStartPoint.

  • Аргумент rowStartPoint используется в функции gp:calculate-Draw-TileRow в качестве начальной точки для центров кругов в ряде.

Для того чтобы компенсировать начальный сдвиг вперед rowStartPoint при рисовании первого ряда (т.е. при первом выполнении цикла while), необходимо слегка передвинуть rowStartPoint в обратном направлении. Благодаря этому удается избежать образования слишком большого пустого пространства между границей дорожки и первым рядом. Точку достаточно сдвинуть на расстояние, равное половине радиуса плитки (TileRadius). Этого можно достичь, используя функцию polar для перемещения точки rowStartPoint вдоль вектора, повернутого на 180 градусов относительно угла PathAngle. При этом точка временно оказывается за пределами границы дорожки.

Следующий фрагмент (слегка измененный для упрощения чтения) также может вызвать затруднения:

(setq tileList (append tileList
                  (gp:calculate-Draw-TileRow
                    (setq rowStartPoint
                      (polar rowStartPoint pathAngle RowSpacing)
                    ) ;_ end of setq
                    TileRadius TileSpace pathWidth pathAngle
                    offsetFromCenter ObjectCreationStyle
                  )))

По сути, он представляет собой вызов функции gp:calculate-Draw-TileRow, вложенный в функцию append, которая, в свою очередь, вложена в функцию setq.

Функция gp:calculate-Draw-TileRow возвращает идентификаторы объектов для построенных плиток. Такими идентификаторами обозначается каждая плитка на чертеже. Плитки строятся ряд за рядом, поэтому функция возвращает идентификаторы объектов сразу для целого ряда. Функция append добавляет их в список tileList.

Ближе к концу функции можно обнаружить следующий фрагмент кода:
(setq offsetFromCenter
  (if (= offsetFromCenter 0.0)
       offsetDistance
       0.0
  )
)

Этот фрагмент отвечает за рисование рядов плиток в шахматном порядке. Здесь происходит либо совмещение центра средней плитки с центральной линией дорожки, либо сдвиг плиток относительно этой линии. Алгоритм выглядит следующим образом:

Установить сдвиг равным следующему:
   Если текущий сдвиг равен 0, установить его равным расстоянию сдвига;
      Иначе, установить сдвиг равным 0.


Построение плиток в ряду

Создание плиток
Создание плиток
Построение плиток в ряду

Теперь, когда понятно, как размещаются ряды плиток на дорожке, следующим этапом является построение плиток внутри каждого ряда. На следующем чертеже показаны два возможных случая: ряд, в котором отступ от центральной линии дорожки равен нулю, и ряд со сдвигом, не равном нулю. Посмотрим на чертеж и ознакомимся с алгоритмом.

Установить переменные StartPoint, angp90, angm90 и т.п.
Установить переменную FirstCenterPoint равной StartPoint + сдвиг
    (который может быть равным 0.0).
Установить начальное значение TileCenterPt равным FirstCenterPoint.
(Пояснение: Начните с построения кругов в направлении angp90)
Пока расстояние от StartPoint до TileCenterPt меньше, чем HalfWidth:
    Построить круг (добавив его в список кругов).
    Установить TileCenterPt на расстоянии шага плитки
    в направлении angm90.
Конец цикла
Установить TileCenterPoint равным FirstCenterPoint + шаг плитки в направлении angm90.
Пока расстояние от StartPoint до TileCenterPt меньше, чем HalfWidth:
    Построить круг (добавив его в список кругов).
    Установить TileCenterPt на расстоянии шага плитки
    в направлении angm90.
Конец цикла
Вернуть список кругов.


Изучение кода

Создание плиток
Создание плиток
Изучение кода

Теперь посмотрим на код функции gp:calculate-Draw-TileRow:

(defun gp:calculate-Draw-TileRow (startPoint TileRadius
          TileSpace pathWidth pathAngle offsetFromCenter
          ObjectCreationStyle / HalfWidth TileDiameter
                        ObjectCreationFunction angp90 angm90
                        firstCenterPt TileCenterPt TileList)
  (setq HalfWidth (- (/ pathWidth 2.00) TileRadius)
     Tilespacing (+ (* TileRadius 2.0) TileSpace)
     TileDiameter (* TileRadius 2.0)
     angp90 (+ PathAngle (Degrees->Radians 90))
     angm90 (- PathAngle (Degrees->Radians 90))
     firstCenterPt (polar startPoint angp90 offsetFromCenter)
     tileCenterPt firstCenterPt
     ObjectCreationStyle(strcase ObjectCreationStyle)
     ObjectCreationFunction
     (cond
          ((equal ObjectCreationStyle "ACTIVEX")
            gp:Create_activeX_Circle
          )
          ((equal ObjectCreationStyle "ENTMAKE")
            gp:Create_entmake_Circle
          )
          ((equal ObjectCreationStyle "COMMAND")
            gp:Create_command_Circle
          )
          (T
            (alert (strcat "ObjectCreationStyle в функции
                   gp:calculate-Draw-TileRow"
                  "\nневерный. Обратитесь к разработчику."
                  "\n ObjectCreationStyle установлен в ACTIVEX"
                   )
            )
             setq ObjectCreationStyle "ACTIVEX")
          )
        )
       )
       ;; Создание окружностей слева от центра.
       (while (< (distance startPoint tileCenterPt) HalfWidth)
         ;; Добавление каждой плитки к возвращаемому списку.
        (setq tileList
             (cons
             (ObjectCreationFunction tileCenterPt TileRadius)
                   tileList
             )
        )
        ;; Расчет центральной точки следующей плитки.
        (setq tileCenterPt
          (polar tileCenterPt angp90 TileSpacing)
        )
       );_ end of while
       ;; Создание окружностей справа от центра.
       (setq tileCenterPt
         (polar firstCenterPt angm90 TileSpacing))
       (while (< (distance startPoint tileCenterPt) HalfWidth)
          ;; Добавление каждой плитки к возвращаемому списку.
          (setq tileList
               (cons
                  (ObjectCreationFunction tileCenterPt TileRadius)
                    tileList
               )
          )
        ;; Расчет центральной точки следующей плитки.
         (setq tileCenterPt (polar tileCenterPt angm90 TileSpacing))
       );_ end of while
       ;; Возврат списка плиток.
       tileList
) ;_ end of defun

Этот AutoLISP-код реализует описанный выше словесный алгоритм и имеет следующие дополнения:

(setq ObjectCreationFunction
  (cond
     ((equal ObjectCreationStyle "ACTIVEX")
        gp:Create_activeX_Circle
     )
     ((equal ObjectCreationStyle "ENTMAKE")
       gp:Create_entmake_Circle
     )
     ((equal ObjectCreationStyle "COMMAND")
       gp:Create_command_Circle
     )
     (T
       (alert
         (strcat
          "ObjectCreationStyle в функции gp:calculate-Draw-TileRow"
          "\nневерная. Обратитесь к разработчику."
          "\n ObjectCreationStyle установлен в ACTIVEX"
         ) ;_ end of strcat
       ) ;_ end of alert
      (setq ObjectCreationStyle "ACTIVEX")
     )
  ) ;_ end of cond
) ;_ end of setq

Вспомним, что плитки (круги) можно строить одним из трех способов: с помощью функций ActiveX, функции entmake и функции command. Переменной ObjectCreationFunction присваивается одна из этих трех функций, в зависимости от параметра ObjectCreationStyle (передаваемого из C:GPath через gp:Calculate-and-Draw-Tiles). Ниже приводятся три функции, как они должны выглядеть в файле gpdraw.lsp:

(defun gp:Create_activeX_Circle (center radius)
  (vla-addCircle *ModelSpace*
    (vlax-3d-point center) ; преобразование в 3D точку радиуса, совместимую с
    ActiveX
  )
) ;_ end of defun
(defun gp:Create_entmake_Circle(center radius)
  (entmake
    (list (cons 0 "CIRCLE") (cons 10 center) (cons 40 radius))
  )
  (vlax-ename->vla-object (entlast))
)
(defun gp:Create_command_Circle(center radius)
  (command "_CIRCLE" center radius)
  (vlax-ename->vla-object (entlast))
)

Первая функция строит круг с помощью функции ActiveX и возвращает объект ActiveX.

Вторая функция строит круг с помощью функции entmake. Она возвращает имя объекта, преобразованное в объект ActiveX.

Третья функция строит круг с помощью функции command. Она также возвращает имя объекта, преобразованное в объект ActiveX.


Проверка работы кода программы

Создание плиток
Создание плиток
Проверка работы кода программы

Если проверка уже сделана, то следующую часть можно пропустить.

Проверка работы кода
  1. Закройте все активные окна в VLISP, включая окна проекта.
  2. Скопируйте полностью содержимое папки Tutorial\VisualLISP\Lesson5 в папку MyPath.
  3. Откройте файл проекта gpath5.prj с помощью меню VLISP "Выбор проекта" » "Открыть проект".
  4. Загрузите исходные файлы проекта.
  5. Активируйте (переключитесь в) окно AutoCAD® и введите команду gpath для запуска программы.
  6. Постройте с помощью gpath три дорожки, каждый раз выбирая разные методы создания объектов. Вы заметили разницу в скорости построения дорожки в зависимости от выбранного метода?


Итоги занятия 5

Создание плиток
Создание плиток
Итоги занятия 5

В начале занятия были рассмотрены:

  • Проверка соответствия скобок в коде.
  • Автоматическое дописывание имен функций.
  • Получение справочной информации о функциях.

Занятие было завершено созданием функции для заполнения дорожки плитками. Теперь программа удовлетворяет требованиям, сформированным в самом начале учебного пособия.

На этом этапе читатели уже имеют достаточно знаний для того, чтобы приступить к самостоятельной разработке программ на VLISP. Однако, для желающих в учебном пособии предлагается еще два занятия для ознакомления с функциями реакторов и другими возможностями среды VLISP.


Использование реакторов

Данное занятие знакомит с тем, что такое реакторы и как они назначаются событиям и объектам чертежа. Реакторы позволяют программе AutoCAD® уведомлять приложение при возникновении определенных событий. Например, если пользователь перемещает объект, к которому прикреплен реактор, приложение получает уведомление о том, что объект был перемещен. При этом можно запрограммировать дополнительные операции — например, перемещение других объектов, связанных с перемещенным или обновление текстовой метки, которая записывает информацию об изменениях объекта или свойства чертежа. Фактически, это равноценно установке пейджера для приложения и настройке AutoCAD на отправку сообщений на этот пейджер в случае возникновения какого-либо события.

Темы:

  • Общие сведения о реакторах
  • Разработка реакторов для парковой дорожки
  • Проверка работы реакторов
  • Итоги занятия 6


Общие сведения о реакторах

Использование реакторов
Использование реакторов
Общие сведения о реакторах

Реактор это объект, прикрепляемый к графическому редактору или определенным объектам на чертеже. Если продолжать сравнение с пейджером, реактор можно считать устройством, которое автоматически звонит на пейджер, если случается что-то значительное. Пейджер в приложении представляет собой функцию AutoLISP®, вызываемую реактором. Такая функция называется функцией обратного вызова.

Примечание Код программы в двух последних занятиях существенно сложнее, чем в занятиях 1-5, и требует от читателя значительно большего уровня подготовки. Здесь дается большой объем информации, при этом не все объясняется так же подробно, как ранее. Если у вас нет опыта, вначале можно столкнуться с трудностями при написании кода. Рассматривайте это как первое знакомство с некоторыми весьма эффективными, но технически сложными возможностями Visual LISP®.

Темы:

  • Типы реакторов


Типы реакторов

Использование реакторов
Использование реакторов
Типы реакторов

Существует много типов реакторов AutoCAD®. Каждый тип реактора соответствует одному или нескольким событиям AutoCAD. Реакторы группируются по следующим категориям:

Реакторы редактора

Сообщают приложению о каждом вызове команды AutoCAD.

Реакторы связывания

Сообщают приложению о каждой загрузке или выгрузке приложения ObjectARX®.

Реакторы базы данных

Соответствуют определенным объектам базы данных чертежа.

Реакторы документов

Сообщают приложению в режиме многооконного интерфейса об изменениях в текущих документах, например, об открытии нового чертежа, активации другого окна документа или об изменении состояния блокировки документа.

Реакторы объектов

Сообщают о каждом изменении, копировании или удалении определенных объектов.

За исключением реакторов редактора, каждой категории соответствует один тип реактора. Реакторы редакторов могут быть разного типа: например, реакторы DXF™ уведомляют приложение об импорте или экспорте DXF файла, реакторы мыши уведомляют о событиях мыши, например, о двукратном нажатии.

Внутри категорий имеется множество различных событий, которым можно назначить реакторы. AutoCAD позволяет пользователям выбирать любое из множества различных действий, после чего можно назначить какому-либо событию реактор и создать функцию обратного вызова, вызываемую при наступлении события. После этого можно прикрепить реактор “auto-dialer” к событию, затем записать функцию обратного вызова, которая запускается при определенном событии.


Разработка реакторов для парковой дорожки

Использование реакторов
Использование реакторов
Разработка реакторов для парковой дорожки

Изучение возможности добавления реакторов в приложение парковой дорожки начнем с рассмотрения всего нескольких событий, не пытаясь полностью охватить возможные действия пользователя.

Темы:

  • Выбор событий для назначения реакторов парковой дорожке
  • Функции обратного вызова
  • Использование нескольких реакторов
  • Назначение реакторов
  • Хранение данных с реактором
  • Обновление функции C:GPath
  • Добавление функций обратного вызова для реакторов
  • Устранение последствий работы реакторов


Выбор событий для назначения реакторов парковой дорожке

Использование реакторов
Использование реакторов
Выбор событий для назначения реакторов парковой дорожке

Поставим следующие цели:

  • При перемещении угловой точки (вершины) парковой дорожки ее границы должны сохранять прямоугольную форму. Плитки на дорожке также должны перерисовываться в соответствии с новыми размерами дорожки.
  • При удалении границы парковой дорожки должны удаляться также все плитки.


Функции обратного вызова

Использование реакторов
Использование реакторов
Функции обратного вызова

Каждому событию, которому назначен реактор, должна соответствовать функция, вызываемая при наступлении события. Логическая последовательность событий, наступающих при перетаскивании пользователем одной из вершин полилинии, описывается следующим алгоритмом.

Описание функции gp:outline-changed
       Удалить плитки.
       Определить, как изменилась граница.
       Исправить границу.
       Перерисовать новые плитки.
Конец функции

Но при этом возникает одна сложность. Когда пользователь перетаскивает контур вершины полилинии, AutoCAD уведомляет приложение о событии :vlr-modified. Однако, пусть пользователь только начал перетаскивать одну из вершин полилинии. Если немедленно вызвать функцию gp:outline-changed, действие пользователя будет прервано в середине. Где именно будет располагаться вершина — еще неизвестно, так как пользователь пока не указал ее новое положение. И, наконец, AutoCAD не позволяет изменять полилинию в то время, когда пользователь перетаскивает какую-либо из ее вершин. Полилиния открыта в AutoCAD для редактирования и будет оставаться в этом состоянии, пока пользователь не закончит перемещение.

В связи с этим подход к программе следует изменить. Приведем обновленный алгоритм:

Когда пользователь начинает перемещение вершины полилинии,
  Вызвать функцию gp:outline-changed
  Описание функции gp:outline-changed
    Описать глобальную переменную, сохраняющую указатель на полилинию,
    изменяемую пользователем.
  Конец функции
После завершения команды
  Вызвать функцию gp:command-ended
  Описание функции gp:command-ended
           Удалить плитки
           Получить информацию о предыдущем расположении вершин полилинии
           Получить информацию о новом расположении вершин полилинии
           Переопределить полилинию (выпрямить ее)
           Перерисовать плитки
  Конец функции

О завершении изменения контура дорожки, AutoCAD уведомляет приложение с помощью события :vlr-commandEnded, если определен редактор реактора.

Использование глобальной переменной для идентификации измененной пользователем полилинии необходимо, так как между функциями gp:outline-changed и gp:command-ended нет связи. В нашем приложении обе функции вызываются и выполняются независимо друг от друга. Глобальная переменная хранит важную информацию, определенную одной функцией и доступную для другой.

Теперь надо подумать о том, что должно происходить, если пользователь удалит границу дорожки. В конечном счете, должны удалиться все плитки. Этот случай описывается следующим алгоритмом:

Когда пользователь начинает удалять границу,
  Вызвать функцию gp:outline-erased
  Описание функции gp:outline-erased
     Описать глобальную переменную, сохраняющую указатель на реактор,
     назначенный удаляемой полилинии
  Конец функции
После завершения удаления
  Вызвать функцию gp:command-ended
  Описание функции gp:command-ended
     Удалить плитки, принадлежавшие только что удаленной полилинии
  Конец функции


Использование нескольких реакторов

Использование реакторов
Использование реакторов
Использование нескольких реакторов

Пользователи, построив на экране несколько парковых дорожек, могут захотеть удалить сразу несколько из них. Необходимо это учесть.

Реактор, назначенный объекту, является реактором объекта. Каждому объекту на чертеже может соответствовать свой реактор. Определенное событие редактирования (такое, например, как команда СТЕРЕТЬ) может вызвать несколько функций обратного вызова, в зависимости от количества выбранных объектов, к которым прикреплены реакторы. Реакторы редактора, с другой стороны, по своей природе всегда задаются по одному. Поэтому приложение всегда назначает только один реактор события :vlr-commandEnded.

Последовательность событий для обоих типов изменений (изменение положения вершины и стирание полилинии) завершается действиями, которые должны выполняться внутри функции gp:command-ended. Определим набор действий, выполняемых для каждого случая. Этот случай описывается следующим алгоритмом:

Описание команды gp:command-ended (2-я версия)
  Извлечь указатель полилинии (из глобальной переменной)
      Условие:
        Если полилиния была изменена, то:
           Удалить плитки
           Получить информацию о предыдущем расположении вершин полилинии
           Получить информацию о новом расположении вершин полилинии
           Переопределить полилинию (выпрямить ее)
           Перерисовать плитки
        Конец условного выражения
        Если полилиния была стерта, то:
           Удалить плитки
        Конец условного выражения
      Конец условного выражения.
Конец функции


Назначение реакторов

Использование реакторов
Использование реакторов
Назначение реакторов

Следующий этап планирования приложения с реакторами определить, как и когда назначить реакторы. Необходимо назначить два объектных реактора полилинии-границе парковой дорожки (один на событие изменения, другой на событие удаления) и один реактор редактора для предупреждения приложения о завершении изменения полилинии. Объектные реакторы прикрепляются объектам, а реакторы редактора регистрируются в AutoCAD.

Необходимо учесть еще одно обстоятельство. Для пересчета контура полилинии и его возврата к прямоугольной форме после его изменения пользователем необходимо знать настройку вершины до изменения. Установить эту информацию после того, как полилиния была изменена, нельзя. Известно только новое расположение точки. Как можно решить проблему? Можно сохранить эту информацию в глобальной переменной, но здесь возникает одно большое неудобство. Пользователи могут нарисовать множество различных парковых дорожек и для каждой из них потребуется своя глобальная переменная. Это может внести путаницу.


Хранение данных с реактором

Использование реакторов
Использование реакторов
Хранение данных с реактором

Проблему сохранения исходной конфигурации можно решить с помощью другой функции реакторов VLISP: возможности хранения данных в реакторе. Для этого надо при построении границы дорожки прикрепить к ней реактор и данные, которые необходимо сохранить. В связи с этим придется изменить главную функцию программы, C:GPath:

Описание функции C:GPath
   Выполнить все, что делала функция ранее для построения парковой дорожки
   (ничего не меняя)
   Назначить полилинии реактор объекта, используя следующие параметры:
      Указатель на только что построенную полилинию,
      Список данных, которые нужно запомнить в реакторе,
      Список отслеживаемых событий, происходящих с полилинией,
      а также вызываемые функции обратного вызова LISP.
   Конец задания объектного реактора
   Прикрепить к графическому редактору реактор, используя
   следующие параметры:
      Все данные, которые необходимо сохранить с реактором (в данном случае их нет)
      Список отслеживаемых событий редактора,
      а также вызываемые функции обратного вызова LISP.
   Конец задания реактора редактора
Конец функции


Обновление функции C:GPath

Использование реакторов
Использование реакторов
Обновление функции C:GPath

Обновим функцию C:GPath, создав в ней реакторы

Для создания реактора в C:GPath
  1. Заменим содержимое файла gpmain.lsp обновленной версией кода. Для этого можно скопировать файл из папки <AutoCAD >\Tutorial\VisualLISP\Lesson6:
    2. (defun C:GPath (/
            gp_PathData
            gp_dialogResults
            PolylineName
            tileList
               )
      (setvar "OSMODE" 0) ;; Отключают объектную привязку
    ;|
    ;; Lesson 6 - добавление фиктивного реактора команды в AutoCAD
      ;; Нежелательно при каждом создании окружности
      ;; использовать КОМАНДУ, предлагающую пользователю выбрать
      ;; метод создания плитки. Поэтому отключите
      ;; *commandReactor*, если он существует.
      |;
    (if *commandReactor*
        (progn
          (setq *commandReactor* nil)
          (vlr-remove-all :VLR-Command-Reactor)
        )
      )
      ;; Запрос на ввод: пути к местоположению и
      ;; направление, затем параметры пути.  Продолжить только при
      ;; действительных входных данных.  Запись данных в функцию gp_PathData.
      (if (setq gp_PathData (gp:getPointInput))
        (if (setq gp_dialogResults
               (gp:getDialogInput
             (cdr (assoc 40 gp_PathData))
               ) ;_ end of gp:getDialogInput
        ) ;_ end of setq
          (progn
        ;; Теперь результаты gp:getPointInput следует присоединить к
        ;; информации, добаленной функцией gp:getDialogInput
        (setq gp_PathData (append gp_PathData gp_DialogResults))
        ;; На этом этапе все данные, указанные пользователем, введены
        ;; Начертить контур с сохранением результирующей полилинии с "указателем"
        ;; на переменную, именуемую PolylineName
        (setq PolylineName (gp:drawOutline gp_PathData))
        ;; Далее, начинается создание плиток в контуре.
        ;; gp_tileList содержит списко указателей объектов для
        ;; плиток. Подсчитывая количество точек (с помощью
        ;; функции length), можно распечатать данные о том, сколько
        ;; было создано плиток.
        (princ "\nДля дорожки необходимо")
        (princ
          (длина
            (setq tileList (gp:Calculate-and-Draw-Tiles gp_PathData))
          ) ;_ end of length
        ) ;_ end of princ
        (princ " плиток.")
    ;; Добавляют список указателей к плиткам (возвращаемый
        ;; gp:Calculate-and-Draw-Tiles) в функцию gp_PathData. Список
        ;; хранится в данных реактора для реактора, присоединенного
        ;; к границе полилинии. По этим данным полилиния
        ;; "знает", что плитки (окружности) принадлежат ей.
        (setq gp_PathData
               (append (list (cons 100 tileList))
                        ; все плитки
                   gp_PathData
               ) ;_ end of append
        ) ;_ end of setq
    ;; Прежде чем назначить данные реактора объекту, рассмотрим
        ;; функцию vlr-object-reactor
        ;; vlr-object-reactor имеет следующие аргументы:
        ;; (vlr-object-reactor - обратный вызов данных владельца)
        ;; Аргумент обратных вызовов представляет собой список, состоящий из
        ;; '(event_name . callback_function)
        ;;
        ;; На этом занятии будут использоваться все аргументы,
        ;; связанные с реактором объекта vlr-object-reactor
        ;; Эти функции реактора выполняются только если
        ;; полилиния будет изменена или удалена в PolylineName
        (vlr-object-reactor
          ;; Первый аргумент для реактора vlr-object-reactor является
          ;; аргументом "Список владельца". Сюда помещается объект,
          ;; который будет связан с
          ;; реактором. В этом случае vlaObject
          ;; хранится в PolylineName.
          (список PolylineName)
          ;; Второй аргумент содержит данные для дорожки
          gp_PathData
          ;; Третий аргумент является списком определенных типо реактора,
          ;; которые требуется использовать
          '
           (
            ;; реактор, вызываемый при изменении объекта
            (:vlr-modified . gp:outline-changed)
            ;; реактор, вызываемый при удалении объекта
            (:vlr-erased . gp:outline-erased)
           )
        ) ;_ end of vlr-object-reactor
        ;; Далее, зарегистрируйте реактор команды для настройки полилинии
        ;; по завершении команды изменения
        (if (not *commandReactor*)
          (setq *commandReactor*
    (VLR-Command-Reactor
               nil ; С реактором команды данные не связаны
               '(
                 (:vlr-commandWillStart . gp:command-will-start)
                 (:vlr-commandEnded . gp:command-ended)
                )
             ) ;_ end of vlr-command-reactor
          )
        )
     ;; Следующий код удаляет все реакторы при закрытии
     ;; чертежа. Это крайне важно!!!!!!!!!
     ;; Без этого уведомления может произойти аварийное завершение работы AutoCAD!
     (if (not *DrawingReactor*)
          (setq *DrawingReactor*
             (VLR-DWG-Reactor
               nil ; С реактором команды данные не связаны
               '((:vlr-beginClose . gp:clean-all-reactors)
                )
             ) ;_ end of vlr-DWG-reactor
         )
     )
          ) ;_ end of progn
          (princ "\nОтмена функции.")
        ) ;_ end of if
        (princ "\nНедостаточно данных для создания границы.")
      ) ;_ end of if
      (princ) ; тихий выход
    ) ;_ end of defun
    ;;; Отображение сообщения с именем команды.
    (princ "\nВведите GPATH для создания парковой дорожки.")
    (princ)
  2. Просмотрите изменения в коде и комментарии к каждому выражению. Все внесенные изменения по сравнению с предыдущим вариантом в данном учебном пособии выделяются полужирным шрифтом.


Добавление функций обратного вызова для реакторов

Использование реакторов
Использование реакторов
Добавление функций обратного вызова для реакторов

Функции обратного вызова для реакторов представляют собой существенное добавление к коду приложения. Код для них имеется в папке Lesson6.

Для добавления функций обратного вызова для реакторов в программу
  1. Скопируйте файл gpreact.lsp из папки Tutorial\VisualLISP\Lesson6 в рабочую папку MyPath.
  2. Откройте проект GPath (если он еще не открыт) и нажмите кнопку "Свойства проекта" в окне проекта парковой дорожки.
  3. Добавьте к проекту файл gpreact.lsp.
  4. Файл gpreact.lsp можно расположить в любом месте между файлами utils.lsp (который должен оставаться первым) и gpmain.lsp (который должен оставаться последним). Переместите файлы, если нужно, и нажмите "OK".
  5. Откройте файл gpreact.lsp, дважды нажав кнопку мыши на его имени в окне проекта gpath.

іПросмотрите записанные в файле комментарии, для того чтобы понять, какие операции в нем выполняется. Следует обратить внимание, что функции обратного вызова являются фиктивными. Единственное, что они пока делают — это вывод предупреждающих сообщений.

Последняя функция в файле является настолько важной, что заслуживает отдельного рассмотрения.


Устранение последствий работы реакторов

Использование реакторов
Использование реакторов
Устранение последствий работы реакторов

Реакторы действительно очень активны. В процессе разработки приложения с использованием реакторов могут часто происходить сбои программы, иногда приводящие к аварийному завершению работыAutoCAD. Поэтому полезно иметь инструмент, позволяющий в случае необходимости убрать все созданные реакторы.

Файл gpreact.lsp содержит функцию gp:clean-all-reactors, которая, сама по себе, выполняет небольшое количество действий. Она служит для вызова функции CleanReactors. Скопируйте эту функцию в конец файла utils.lsp:

;;;--------------------------------------------------------------;
;;; Функция: CleanReactors ;
;;;--------------------------------------------------------------;
;;; Описание: Общая служебная функция, применяемая для очистки ;
;;; реакторов. Она может использоваться во время отладки, ;
;;; а также при удалении любых открытых реакторов ;
;;; перед закрытием чертежа. ;
;;;--------------------------------------------------------------;
(defun CleanReactors ()
  (mapcar 'vlr-remove-all
         '(:VLR-AcDb-reactor
           :VLR-Editor-reactor
           :VLR-Linker-reactor
           :VLR-Object-reactor
          )
  )
)


Проверка работы реакторов

Использование реакторов
Использование реакторов
Проверка работы реакторов

Теперь все готово, чтобы использовать реакторы в приложении.

Для проверки работы кода реакторов
  1. Загрузите все исходные файлы проекта. Для этого нажмите кнопку "Загрузка исходных файлов" в окне проекта.
  2. Вызовите и выполните функцию C:GPath.

    3. Программа строит парковую дорожку так же, как на занятии 5. На первый взгляд ничего необычного не происходит.

  3. После того как дорожка нарисована, попробуйте выполнить следующие действия:
    • Переместите вершину полилинии. Для этого выберите полилинию и перетащите одну из вершин за ручку.
    • Растяните полилинию.
    • Передвиньте полилинию.
    • Сотрите полилинию.

Посмотрите на появляющиеся сообщения. Они разъясняют, что происходит при выполнении операции внутри приложения.

Если приложение работает неверно и при этом нет времени на отладку кода, можно воспользоваться кодом, имеющимся в папке Tutorial\VisualLISP\Lesson6. В этой папке следует выбрать проект Gpath6.

Примечание После проверки работы реакторов может случиться так, что переключение из AutoCAD в VLISP перестанет осуществляться с помощью сочетания клавиш ALT + TAB или нажатия кнопки мыши в окне VLISP. Если это произойдет, то для возврата в VLISP следует просто ввести в командной строке AutoCAD vlisp.

Темы:

  • Подробное изучение поведения реакторов


Подробное изучение поведения реакторов

Использование реакторов
Использование реакторов
Подробное изучение поведения реакторов

Теперь нужно запастись стопкой бумаги для записи событий реакторов, происходящих внутри приложения. Здесь приведен пример того, как следует вести записи:

Нарисуйте десять парковых дорожек, затем проверьте следующие сочетания "команда/объект", последовательно выбирая полилинии:

  • Стереть/Полилиния границы (дорожка 1)
  • Стереть/Круг внутри полилинии (дорожка 2)
  • Стереть/Две полилинии (дорожки 3 и 4)
  • Перенести/Полилиния границы (дорожка 5)
  • Перенести/Круг внутри полилинии (дорожка 6)
  • Перенести/Две полилинии и несколько кругов (дорожки 7 и 8)
  • Переместить вершину (с помощью ручек)/Полилиния границы (дорожка 9)
  • Растянуть/Полилиния границы (дорожка 10)

Это упражнение позволит лучше понять, что происходит при работе приложения. В любое время при выполнении занятия 7, если функциональные возможности реактора вызывают сомнения, можно просмотреть “трассировочные ведомости реакторов.”


Итоги занятия 6

Использование реакторов
Использование реакторов
Итоги занятия 6

Это занятие было посвящено реакторам AutoCAD и их применению с помощью VLISP. Был намечен план создания реакторов в приложении построения парковой дорожки и сделаны первые шаги для осуществления этого плана.

Реакторы могут увеличивать функциональные возможности приложения, но следует помнить - чем эффективнее программа, тем серьезнее будут последствия ее аварийного отказа.

Кроме того, следует помнить, что назначение реакторов, описанное здесь, действует только в пределах одного рабочего сеанса. Реакторы, назначенные парковой дорожке, не будут работать при последующем открытии чертежа. Более подробную информацию о добавлении различных реакторов можно найти в разделе “Transient versus Persistent Reactors” ("Различные типы реакторов") в документе AutoLISP Developer's Guide ("Руководство разработчика AutoLISP), а сведения о базовых функциях - в документе AutoLISP Reference ("Справочник по AutoLISP").


Окончательная компоновка

На занятии 6 мы изучили основы создания приложений с использованием реакторов. На седьмом занятии полученные знания будут применены на практике. После проверки работы приложения создадим из проекта VLISP приложение Visual LISP®.

Данная часть учебного пособия предназначена для опытных пользователей. При отсутствии опыта, возможно, представленный здесь код AutoLISP® будет непонятен. В конце занятия приводится перечень книг по AutoLISP, в которых описанные здесь концепции рассматриваются более подробно.

Темы:

  • Общее планирование реакторного процесса
  • Расширение возможностей реакторов
  • Переопределение полилинии-границы
  • Окончательный код программы
  • Сборка приложения
  • Итоги учебного пособия
  • Книги по LISP и AutoLISP


Общее планирование реакторного процесса

Окончательная компоновка
Окончательная компоновка
Общее планирование реакторного процесса

На этом занятии нам придется описать несколько новых функций. Но основной упор будет сделан не на подробное рассмотрение нового кода, а на изложение общих концепций, которые служат базой для программы. Целью данного занятия является создание законченного приложения, аналогичного тому, что использовалось для построения парковой дорожки на занятии 1.

Примечание В процессе разработки и отладки приложений на основе реакторов существует вероятность перехода AutoCAD® в нестабильное состояние. Это может быть вызвано различными причинами например, невозможности удалить реакторы удаленных объектов. Поэтому рекомендуется перед началом 7 занятия сохранить все открытые файлы, закрыть VLISP, выйти из AutoCAD и перезапустить оба приложения.

Начнем с того, что загрузим проект в том виде, каким он стал к концу шестого занятия.

В приложении парковой дорожки осталось выполнить две основных задачи:

  • Написать функции обратного вызова для реактора объекта.
  • Написать функции обратного вызова для реактора редактора.

Необходимо также решить, каким образом программа будет обращаться с глобальными переменными. Часто требуется, чтобы глобальные переменные сохраняли свои значения в течении всего сеанса AutoCAD. Однако в случае с реакторами это не так. Чтобы понять почему, представим себе, что пользователь построил с помощью нашего приложения несколько парковых дорожек в одном чертеже. После этого пользователь начал стирать их, сначала по одной, затем по две и т.д., до тех пор пока все дорожки, кроме одной, не оказались стертыми.

На занятии 5 была введена глобальная переменная *reactorsToRemove*, ответственная за хранение указателей на реакторы для удаляемых полилиний. Объявление переменной *reactorsToRemove* в функции gp:outline-erased означает, что полилиния сейчас будет удалена. В действительности же полилиния удаляется только после вызова функции gp:command-ended.

При первом стирании полилинии все работает правильно. Указатель на реактор хранится в gp:outline-erased. При вызове функции gp:command-ended удаляются все плитки, связанные с полилинией, к которой был прикреплен реактор. После этого пользователь решает удалить две дорожки. В результате приложение получает два вызова в gp:outline-erased, по одному для каждой удаляемой полилинии. Здесь возникают две потенциальные проблемы:

  • При выполнении функции setq для переменной *reactorsToRemove* необходимо добавить к этой переменной указатель на реактор, не стирая уже имеющихся в ней значений. Это означает, что переменная *reactorsToRemove* должна иметь структуру списка, в который можно добавлять указатели на реакторы. Таким образом, можно будет хранить несколько указателей на реакторы по количеству дорожек, удаляемых пользователем с помощью одной команды.
  • При каждом вызове функции gp:command-will-start, обозначающей начало новой последовательности команд, необходимо сбрасывать значение переменной *reactorsToRemove* в nil. Это необходимо, чтобы глобальная переменная не хранила указатели на реакторы после предыдущих команд "Стереть".

    • Невыполнение этих требований (корректная структура данных в виде списка и сброс значений глобальной переменной) может привести к непредсказуемому поведению программы. В случае реакторов непредвиденные результаты могут стать причиной неустранимой ошибки в сеансе AutoCAD.

Ниже приведена цепь событий при стирании пользователем двух парковых дорожек одной командой. Следует обратить внимание на глобальные переменные:

  • Вызвать команду стереть. Запускается функция gp:command-will-start. Переменной *reactorsToRemove* присваивается значение nil.
  • Выбрать две полилинии. Приложение еще не получило сигнала.
  • Нажмите ENTER для удаления двух выбранных полилиний.

    • Приложение получает отклик на gp:outline-erased для одной из полилиний. Указатель на реактор добавляется в пустую глобальную переменную *reactorsToRemove*.

    Приложение получает отклик на gp:outline-erased для второй полилинии. Добавьте ее указатель на реактор к глобальной переменной *reactorsToRemove*, которая уже содержит один указатель на реактор.

  • AutoCAD удалит полилинии.
  • Запускается функция обратного вызова gp:command-ended. Все плитки, связанные с указателями на реакторы, хранящимися в *reactorsToRemove*, удаляются.

Кроме глобальной переменной *reactorsToRemove* приложение также содержит глобальную переменную *polyToChange* которая хранит указатели на изменяемые полилинии. Две дополнительные глобальные переменные для приложения будут представлены позже на этом занятии.

Темы:

  • Реакция на вызываемые пользователем команды
  • Хранение информации с реакторами


Реакция на вызываемые пользователем команды

Окончательная компоновка
Окончательная компоновка
Реакция на вызываемые пользователем команды

При написании приложения с реакторами необходимо учесть все команды, которые могут повлиять на объекты. Необходимо также просмотреть все возможные команды редактирования AutoCAD и определить, каким образом приложение должно реагировать на каждую из них. Планируя поведение программы, можно опираться на записи, аналогичные рассмотренным в конце занятия 6. Для этого нужно представить, какие команды может вызвать пользователь, а затем, запуская каждую из них, записать, как на них должно реагировать приложение. Необходимо запланировать также следующее:

  • Поведение программы при вызове пользователем команд ОТМЕНИТЬ и ПОВТОРИТЬ.
  • Поведение программы при вызове пользователем команды ОЙ после удаления объектов, связанных с реакторами.

Чтобы не превращать сложное в слишком сложное, в данном учебном пособии не делается попыток предусмотреть все возможные случаи. Поэтому на этом занятии рассматривается абсолютный минимум возможностей.

Хотя создавать полный спектр функциональных возможностей для дополнительных команд не планируется, проверьте работу нескольких дополнительных функций редактирования:

  • При растягивании полилинии границы дорожки (с помощью команды РАСТЯНУТЬ) может произойти несколько вещей. Полилиния может быть растянута в любом направлении, а не только вдоль большой и маленькой сторон; в итоге она может приобрести весьма произвольную форму. Кроме того, необходимо учесть, сколько вершин было перемещено. Так, результат растягивания при перемещении одной вершины будет отличаться от того, что происходит, если переместить сразу две вершины. В любом случае необходимо стереть старые плитки, а затем, определив изменения границы, пересчитать новые положения плиток.
  • Если пользователь перемещает полилинию границы, необходимо стереть все имеющиеся плитки, а затем построить их в новом месте. Это достаточно простая операция, так как полилиния не изменила ни размера, ни формы.
  • Если пользователь масштабирует полилинию границы, необходимо решить, масштабировать ли вместе с ней плитки, чтобы не изменилось их количество, или, оставив прежний размер, изменить количество плиток. Если пользователь поворачивает полилинию границы, необходимо стереть все имеющиеся плитки, а затем построить их, ориентировав по-новому.
  • Если пользователь поворачивает полилинию границы, необходимо стереть все имеющиеся плитки, а затем построить их, ориентировав по-новому.

Для начала можно, например, сделать следующее:

  • Предупреждать пользователя при вызове команд редактирования (РАСТЯНУТЬ, ПЕРЕНЕСТИ или ПОВЕРНУТЬ) о последствиях их применения для парковой дорожки.
  • Если пользователь продолжает выполнение команды, удалить плитки и не перерисовывать их более.
  • Удалить реакторы с полилинии границы.

Примечание Помимо вызываемых пользователями команд AutoCAD объекты также можно изменить или удалить с помощью приложений AutoLISP или ObjectARX®. В примере, описываемом в данном учебном пособии, не рассматривается случай программного вмешательства, например, через функцию (entdel <полилиния>). В этом случае события :vlr-commandWillStart и :vlr-commandEnded не наступают.


Хранение информации с реакторами

Окончательная компоновка
Окончательная компоновка
Хранение информации с реакторами

Важно продумать, какой тип информации следует подключить к объектным реакторам, создаваемым для каждой полилинии. На занятии 6 реактору назначалось содержимое переменной gp_PathData (ассоциативный список). Кроме того, к ассоциативному списку функции gp_PathData был добавлен новый элемент с ключевым кодом 100. Это список указателей на все круги, расположенные внутри границы дорожки.

Так как необходимо выполнить пересчет полилинии, к переменной gp_pathData добавляются четыре дополнительных ключевых значения:

;;; StartingPoint ;
;;; (12 . BottomStartingPoint) 15------------------------14 ;
;;; (15 . TopStartingPoint) | | ;
;;; EndingPoint 10 ----pathAngle---> 11 ;
;;; (13 . BottomEndingPoint) | | ;
;;; (14 . TopEndingPoint) 12------------------------13 ;
;;; ;

Пронумерованные точки необходимы для пересчета границы дорожки, когда пользователь перетаскивает одну из угловых ручек в новое положение. Эта информация уже имеется в функции gp:drawOutline (файл gpdraw.lsp). Однако посмотрим на значение, возвращаемое функцией. Пока возвращается только указатель на объект полилинии. Поэтому необходимо выполнить три дополнительных действия:

  • Сгруппировать точки периметра в подходящем формате.
  • Изменить функцию так, чтобы она возвращала списки точек периметра и указатель на полилинию.
  • Изменить функцию C:GPath для обработки нового формата значений, возвращаемых функцией gp:drawOutline.

Группировка списков точек периметра является простой задачей. Посмотрим на код функции gp:drawOutline. Локальной переменной p1 соответствует код 12, p2 - 13, p3 - 14 и p4 - 15. Для того чтобы объединить эту информацию, используется следующий вызов функции:

(setq polyPoints(list
  (cons 12 p1)
  (cons 13 p2)
  (cons 14 p3)
  (cons 15 p4)
))

Изменить функцию так, чтобы она возвращала списки точек периметра и указатель на полилинию, также просто. Для этого в последнем выражении функции gp:drawOutline нужно собрать в один список две величины.

(list pline polyPoints)
Добавление в программу кода для сохранения точек периметра полилинии
  1. Измените функцию gp:drawOutline, выполнив изменения, выделенные жирным шрифтом в следующем коде (обратите внимание на добавление локальной переменной polyPoints к утверждению defun):
    2. (defun gp:drawOutline (BoundaryData / PathAngle
                   Width HalfWidth StartPt PathLength
                   angm90 angp90 p1 p2
                   p3 p4 poly2Dpoints
                   poly3Dpoints plineStyle pline
                   polyPoints
                   )
      ;; выделение значений из списка BoundaryData.
      (setq PathAngle (cdr (assoc 50 BoundaryData))
        Width (cdr (assoc 40 BoundaryData))
        HalfWidth (/ Width 2.00)
        StartPt (cdr (assoc 10 BoundaryData))
        PathLength (cdr (assoc 41 BoundaryData))
        angp90 (+ PathAngle (Degrees->Radians 90))
        angm90 (- PathAngle (Degrees->Radians 90))
        p1 (polar StartPt angm90 HalfWidth)
        p2 (polar p1 PathAngle PathLength)
        p3 (polar p2 angp90 Width)
        p4 (polar p3 (+ PathAngle
                     (Degrees->Radians 180)) PathLength)
        poly2Dpoints (apply 'append
                    (mapcar '3dPoint->2dPoint (list p1 p2 p3 p4))
                    )
        poly3Dpoints (mapcar 'float (append p1 p2 p3 p4))
        ;; получить стиль полилинии.
        plineStyle (strcase (cdr (assoc 4 BoundaryData)))
        ;; Добавление полинии к пространству модели автоматически с использованием ActiveX.
        pline (if (= plineStyle "LIGHT")
                   ;; создание компактной полилинии.
                   (vla-addLightweightPolyline
                 *ModelSpace*; Глобальное определение пространства модели
                 (gp:list->variantArray poly2Dpoints)
                        ;преобразование данных
                 ) ;_ end of vla-addLightweightPolyline
                  ;; или создание обычной полилинии.
                   (vla-addPolyline
                 *ModelSpace*
                 (gp:list->variantArray poly3Dpoints)
                        ;преобразование данных
                 ) ;_ end of vla-addPolyline
                   ) ;_ end of if
    polyPoints (list
                   (cons 12 p1)
                   (cons 13 p2)
                   (cons 14 p3)
                   (cons 15 p4)
                   )
        ) ;_ end of setq
      (vla-put-closed pline T)
      (list pline polyPoints)
    ) ;_ end of defun
  2. Измените функцию C:GPath (файл gpmain.lsp). Найдите строку кода, которая сейчас выглядит так:
    3. (setq PolylineName (gp:drawOutline gp_PathData))

    Измените ее следующим образом:

    (setq PolylineList (gp:drawOutline gp_PathData)
          PolylineName (car PolylineList)
          gp_pathData (append gp_pathData (cadr PolylineList))
     ) ;_ end of setq

    Переменная gp_PathData теперь содержит всю необходимую для функции реактора информацию.

  3. Добавьте PolylineList в раздел описаний локальных переменных функции C:GPath.


Расширение возможностей реакторов

Окончательная компоновка
Окончательная компоновка
Расширение возможностей реакторов

На занятии 6 мы назначили функцию обратного вызова gp:command-will-start реакторному событию :vlr-commandWillStart. На текущем этапе функция только выводит некоторые сообщения и присваивает двум глобальным переменным *polyToChange* и *reactorsToRemove* значение nil.

Расширение возможностей функции обратного вызова gp:command-will-start
  1. Откройте файл gpreact.lsp.
  2. Добавьте две переменные в функции gp:command-will-start для вызова функции setq. Для этого выполните следующее:
    3. ;; Сброс всех четырех глобальных переменных реактора в nil.
    (setq *lostAssociativity* nil
            *polyToChange* nil
            *reactorsToChange* nil
            *reactorsToRemove* nil)
  3. Замените оставшийся код в gp:command-will-start до последнего вызова функции princ следующим кодом:
    4. (if (member (setq currentCommandName (car command-list))
           '( "U" "UNDO" "STRETCH" "MOVE"
              "ROTATE" "SCALE" "BREAK" "GRIP_MOVE"
              "GRIP_ROTATE" "GRIP_SCALE" "GRIP_MIRROR")
        ) ;_ end of member
        (progn
          (setq *lostAssociativity* T)
          (princ "\nПРИМЕЧАНИЕ: ")
          (princ currentCommandName)
          (princ " команда разрывает ассоциативность дорожки .")
        ) ;_ end of progn
    ) ;_ end of if

    Здесь производится проверка, не запустил ли пользователь команду, разрушающую ассоциативность между плитками и границей дорожки. Если это произошло, программа устанавливает глобальную переменную *lostAssociativity* и выдает пользователю предупреждение.

    Поэкспериментировав с приложением построения парковой дорожки, можно выявить дополнительные команды редактирования, способные внести изменения в нарисованную дорожку и привести к потере ассоциативности. Эти команды также следует занести в список, чтобы предупредить пользователя о последствиях их применения. Функция вызывается, когда пользователь запустил команду, но еще не выбрал объекты для изменения. В этот момент пользователь еще может прервать выполнение команды.

Темы:

  • Доработка функций обратного вызова для объектных реакторов
  • Разработка функции обратного вызова gp:command-ended
  • Обработка нескольких типов объектов
  • Использование методов ActiveX в функциях обратного вызова реакторов
  • Обработка нелинейных последовательностей реакторов
  • Написание функции command-ended
  • Обновление функции gp:Calculate-and-Draw-Tiles
  • Изменение других вызовов функции gp:Calculate-and-Draw-Tiles


Доработка функций обратного вызова для объектных реакторов

Окончательная компоновка
Окончательная компоновка
Доработка функций обратного вызова для объектных реакторов

На занятии 6 были зарегистрированы две функции обратного вызова на события объектных реакторов. Функция gp:outline-erased была связана с событием реактора :vlr-erased, функция gp:outline-changed была связана с событием :vlr-modified. Теперь необходимо заставить эти функции работать.

Для доработки функций обратного вызова объектных реакторов
  1. В файле gpreact.lsp измените функцию gp:outline-erased так, чтобы она выглядела следующим образом:
    2. (defun gp:outline-erased (outlinePoly reactor parameterList)
      (setq *reactorsToRemove*
           (cons reactor *reactorsToRemove*))
      (princ)
    ) ;_ end of defun

    Здесь выполняется только одна операция. Реактор, назначенный полилинии, сохраняется в списке реакторов для удаления. (Обратите внимание: хотя реакторы и прикрепляются к объектам, они полностью самостоятельны, поэтому их отношения с объектами чертежа необходимо поддерживать так же, как и для обычных объектов AutoCAD).

  2. Внесите следующие изменения в функцию gp:outline-changed:
    3. (defun gp:outline-changed (outlinePoly reactor parameterList)
      (if *lostAssociativity*
        (setq *reactorsToRemove*
                 (cons reactor *reactorsToRemove*))
        (setq *polytochange* outlinePoly
                  *reactorsToChange* (cons reactor *reactorsToChange*))
        )
      (princ)
    )

    Существуют две категории функций, способных изменить полилинию-границу. Первая категория содержит команды, разрывающие ассоциативность дорожки и составляющих ее плиток. Это условие проверялось в gp:command-will-start и, соответственно, задавалась глобальная переменная *lostAssociativity*. В этом случае плитки необходимо удалить, затем пользователь может выполнять с дорожкой все необходимые операции. Ко второй категории относится режим работы с ручками команды РАСТЯНУТЬ, где ассоциативность сохраняется и необходимо выровнять полилинию-границу после перетаскивания вершины в новое положение.

    Переменная *polyToChange* хранит указатель на саму полилинию. Она используется функцией gp:command-ended для пересчета полилинии-границы.


Разработка функции обратного вызова gp:command-ended

Окончательная компоновка
Окончательная компоновка
Разработка функции обратного вызова gp:command-ended

Функция gp:command-ended (реактор редактора) выполняет основные действия в нашей программе. До вызова этой функции полилинии границы парковой дорожки “открыты для изменений;”, то есть пользователи могут изменять границы в AutoCAD. В последовательности реакторов перед выполнением действий необходимо подождать, прежде чем AutoCAD завершит свою часть работы.

Действие функции gp:command-ended словесно описывается следующим алгоритмом:

Определить состояние полилинии.
  УСЛОВИЕ 1 - ПОЛИЛИНИИЯ СТЕРТА (Командой СТЕРЕТЬ)
    Удалить плитки.
  УСЛОВИЕ 2 - ПОТЕРЯНА АССОЦИАТИВНОСТЬ (ПЕРЕНЕСТИ, ПОВЕРНУТЬ и т.д.)
    Удалить плитки.
  УСЛОВИЕ 3 - РАСТЯГИВАНИЕ РУЧКАМИ - ПЕРЕРИСОВКА КОНТУРА И ПЛИТОК
    Удалить плитки.
    Извлечь текущие данные о границе полилинии.
    Если это компактная полилиния,
       обрабатывать данные о границе как 2М
    Иначе
       обрабатывать данные о границе как 3D
    Конец Если
  Переопределить границу (передать ей параметры текущего
         и старого состояния границы).
  Получить новую информацию о границе и записать ее в формате,
         используемом для задания полилинии.
  Регенерировать полилинию.
  Перерисовать плитки (с помощью ActiveX).
  Поместить переработанную информацию обратно в реактор
         с именем *reactorsToChange*.
Конец функции

Алгоритм достаточно понятен, однако в нем скрыто несколько важных деталей, на которых следует заострить внимание.


Обработка нескольких типов объектов

Окончательная компоновка
Окончательная компоновка
Обработка нескольких типов объектов

Во-первых, приложение может строить полилинии двух типов: обычные и компактные. Данные о различных типах полилиний возвращаются в различных форматах. Так, для обычных полилиний возвращается список из двенадцати вещественных чисел: координаты X, Y и Z четырех вершин. Для компактных полилиний возвращается список из восьми вещественных чисел: координаты X и Y четырех вершин.

Для того чтобы найти положение границы измененной дорожки после перемещения одной из вершин полилинии, необходимо выполнить некоторые вычисления. Вычисления значительно упрощаются, если данные о полилинии имеют постоянный формат.

Файл utils.lsp занятия 7 содержит функции для выполнения необходимых преобразований формата: функция xyzList->ListOfPoints извлекает и форматирует списки 3D точек в список списков, а функция xyList->ListOfPoints делает то же самое со списками 2М точек.

Добавление кода для преобразования данных полилинии в постоянных формат
  1. Если файл utils.lsp открыт в окне текстового редактора VLISP, закройте его.
  2. Скопируйте версию файла utils.lsp из папки Tutorial\VisualLISP\Lesson7 в рабочую папку.

    3. Помимо двух функций преобразования формата данных о полилиниях, файл utils.lsp содержит служебные функции, необходимые для поддержания пользовательских изменений в парковой дорожке.

  3. Откройте файл utils.lsp в текстовом редакторе VLISP и просмотрите новый код.


Использование методов ActiveX в функциях обратного вызова реакторов

Окончательная компоновка
Окончательная компоновка
Использование методов ActiveX в функциях обратного вызова реакторов

Вторым важным моментом в приведенном выше словесном описании алгоритма является этап перерисовки плиток. В этом алгоритме ему соответствует следующая строка:

Перерисовать плитки (с помощью ActiveX)

Следует обратить внимание на выражение в скобках: с помощью ActiveX. Почему именно так? Почему приложение не может использовать настройку создания объектов, хранящуюся в ассоциативном списке?

Ответ заключается в том, что функция command не может быть использована для создания объектов внутри функции обратного вызова реактора. Это связано с внутренними ограничениями AutoCAD. Поэтому необходимо использовать для перерисовки плиток ActiveX. Подробнее об этом будет рассказано позже на данном занятии.


Обработка нелинейных последовательностей реакторов

Окончательная компоновка
Окончательная компоновка
Обработка нелинейных последовательностей реакторов

Последняя важная деталь связана с необычной последовательностью "команда/реактор" при редактировании полилинии в AutoCAD с помощью специальных команд работы с ручками. Команды работы с ручками доступны из контекстного меню после выбора ручки на объекте с последующим нажатием правой кнопкой мыши. Последовательность реакторов при этом не является линейной, как в случае команд ПЕРЕНЕСТИ или СТЕРЕТЬ. Фактически, пользователь, не завершив одну команду, переключается на другую. Для разъяснения этой ситуации можно загрузить код из занятия 6, отслеживающий последовательность реакторных событий. Можно также просто просмотреть следующие сообщения, выводимые в окне консоли VLISP:

;; Сначала выберите полилинию и несколько окружностей с помощью
;; рамки выделения. Элементы в наборе объектов:
;; выбранные окружности и полилиния, теперь показаны вместе с ручками.
;; Для вызова последовательности нажмите одну из ручек полилинии:
(GP:COMMAND-WILL-START #<VLR-Command-reactor> (GRIP_STRETCH))
;; Теперь измените команду для перемещения, нажав правую кнопку мыши и выбрав
;; "Перенести" в раскрывающемся меню. Обратите внимание, что запускает реактор command-ended
;; для закрытия команды GRIP_STRETCH
;; без запуска события реактора объекта:
(GP:COMMAND-ENDED #<VLR-Command-reactor> (GRIP_STRETCH))
(GP:COMMAND-WILL-START #<VLR-Command-reactor> (GRIP_MOVE))
;; Перетащите контур (и выбранные окружности) в новое положение.
(GP:OUTLINE-CHANGED #<VLA-OBJECT IAcadLWPolyline 028f3188>
                 #<VLR-Object-reactor> nil)
(GP:COMMAND-ENDED #<VLR-Command-reactor> (GRIP_MOVE))

Как видно из показанного примера, нельзя быть уверенным, что функции обратного вызова для объектных реакторов вызываются во всех случаях.

Данная последовательность имеет некоторые отличия от обычной. Даже во время последнего отклика на окончание команды круги, являющиеся выбранными, не удаляются. Эти круги все еще открыты программой AutoCAD. Попытка удалить их с помощью функции обратного вызова на окончание команды может привести к сбою в AutoCAD. Для того чтобы избежать этого, можно воспользоваться другой глобальной переменной, хранящей список указателей на объекты-плитки до тех пор, пока последние не будут удалены.

Для обработки нелинейной реакторной последовательности
  1. Добавьте в файл gpreact.lsp следующую функцию:
    2. (defun gp:erase-tiles (reactor / reactorData tiles tile)
      (if (setq reactorData (vlr-data reactor))
        (progn
          ;; Плитки в дорожке сохраняются как данные в реакторе.
          (setq tiles (cdr (assoc 100 reactorData)))
          ;; Удаление все существующих плиток в дорожке.
          (foreach tile tiles
              (if (and (null (member tile *Safe-to-Delete*))
                     (not (vlax-erased-p tile))
                     )
                  (progn
                     (vla-put-visible tile 0)
                     (setq *Safe-to-Delete* (cons tile *Safe-to-Delete*))
                  )
              )
          )
          (vlr-data-set reactor nil)
          )
        )
      )

    Эта функция будет использоваться на первом этапе удаления плиток. Обратите внимание, что плитки в действительности не стираются: они становятся невидимыми и добавляются в глобальную переменную *Safe-to-Delete*.

  2. Добавьте в файл gpreact.lsp следующую функцию:
    3. (defun Gp:Safe-Delete (activeCommand)
      (if (not (equal
          (strcase (substr activeCommand 1 5))
            "GRIP_"
          )
      )
      (progn
         (if *Safe-to-Delete*
            (foreach Item *Safe-to-Delete*
              (if (not (vlax-erased-p Item))
                (vla-erase item)
              )
            )
          )
          (setq *Safe-to-Delete* nil)
          )
        )
      )

    Эту функцию можно вызвать только тогда, когда не выполняется команда GRIP_MOVE или GRIP_STRETCH.


Написание функции command-ended

Окончательная компоновка
Окончательная компоновка
Написание функции command-ended

Теперь, познакомившись с алгоритмом и остановившись на некоторых важных деталях, можно заменить фиктивную функцию обратного вызова gp:command-ended на следующую:

(defun gp:command-ended (reactor command-list
             / objReactor
             reactorToChange reactorData
             coordinateValues currentPoints
             newReactorData newPts
             tileList
             )
  (cond
    ;; УСЛОВИЕ 1 - ПОЛИЛИНИИЯ СТЕРТА (Командой СТЕРЕТЬ)
    ;; Если одна или несколько границ полилиний удалены (указано
    ;; наличием *reactorsToRemove*), удалить плитки
    ;; в пределах границы, затем удалить реактор.
    (*reactorsToRemove*
     (foreach objReactor *reactorsToRemove*
       (gp:erase-tiles objReactor)
       )
     (setq *reactorsToRemove* nil)
     )
    ;; УСЛОВИЕ 2 - ПОТЕРЯНА АССОЦИАТИВНОСТЬ (ПЕРЕНЕСТИ, ПОВЕРНУТЬ и т.д.)
    ;; Если ассоциативность потеряна(отмена, перемещение и т.д.), то
    ;; удалить плитки в пределах каждой границы
    ;;
    ((and *lostassociativity* *reactorsToChange*)
     (foreach reactorToChange *reactorsToChange*
       (gp:erase-tiles reactorToChange)
       )
     (setq *reactorsToChange* nil)
     )
    ;; УСЛОВИЕ 3 - GRIP_STRETCH
    ;; В этом случае ассоциативность плиток к дорожке
    ;; сохраняется, но дорожку и плитки необходимо повторно
    ;; рассчитать и вычертить. GRIP_STRETCH может выполняться
    ;; только для одной ПОЛИЛИНИИ одновременно.
    ((and (not *lostassociativity*)
      *polytochange*
      *reactorsToChange*
      (member "GRIP_STRETCH" command-list)
      ;; для GRIP_STRETCH имеется только один реактор в
      ;; глобальной переменной *reactorsToChange*.
      (setq reactorData
         (vlr-data (setq reactorToChange
                  (car *reactorsToChange*)
                 )
               )
        )
      )
     ;; Сначала удалить плитки в пределах границы полилинии.
     (gp:erase-tiles reactorToChange)
     ;; Затем получить текущие значения координат вершин
     ;; полилинии.
     (setq coordinateValues
        (vlax-safearray->list
          (vlax-variant-value
        (vla-get-coordinates *polyToChange*)
        )
          )
       )
     ;; Если контур является компактной полилинией, то имеются
     ;; 2d точки, поэтому использовать функцию xyList->ListOfPoints
     ;; для преобразования координатных данных в список точек
     ;; ((x y) (x y) ...). Иначе использовать
     ;; функцию xyzList->ListOfPoints, работающую
     ;; с 3d точками, и преобразовывающую координатные данные в
     ;; список точек ((x y z) (x y z) ... ).
     (setq CurrentPoints
        (if (= (vla-get-ObjectName *polytochange*) "AcDbPolyline")
          (xyList->ListOfPoints coordinateValues)
          (xyzList->ListOfPoints coordinateValues)
          )
       )
     ;; Отправить новую информацию в функцию RedefinePolyBorder -- она
     ;; возвратит новую границу полилинии
     (setq NewReactorData
        (gp:RedefinePolyBorder CurrentPoints reactorData)
       )
     ;; Получить все точки границы и ...
     (setq newpts (list (cdr (assoc 12 NewReactorData))
            (cdr (assoc 13 NewReactorData))
            (cdr (assoc 14 NewReactorData))
            (cdr (assoc 15 NewReactorData))
            )
       )
     ;; ...обновить контур полилинии с учетом новых точек,
     ;; рассчитанных выше. Если используется компактная полилиния,
     ;; преобразовать эти точки в 2D (так как все точки в
     ;; newpts являются 3D точками),  иначе оставить их как есть.
     (if (= (cdr (assoc 4 NewReactorData)) "LIGHT")
       (setq newpts (mapcar '(lambda (point)
                   (3dPoint->2dPoint Point)
                   )
                newpts
                )
         )
       )
     ;; Обновить полилинию с учетом правильных точек.
     (vla-put-coordinates
      *polytochange*
     ;; Описание списка ->variantArray см. в utils.lsp.
      (gp:list->variantArray (apply 'append newpts))
    )
     ;; Использовать текущее определение NewReactorData,
     ;; представляющей фактически структуру парковой
     ;; дорожки. Единственное исключение - значение поля (100),
     ;; содержащего список плиток, равно nil. Это нормально, так как
     ;; для gp:Calculate-and-Draw-Tiles это поле не требуется
     ;; для создания плиток. Фактически эта функция создает плитки
     ;; и возвращает список вычерченных плиток.
     (setq tileList (gp:Calculate-and-Draw-Tiles
              ;; список данных дорожки без корректного списка плиток
              NewReactorData
              ;; Функция создания объектов
              ;; В реакторе этой функцией *ДОЛЖЕН* быть ActiveX
              "ActiveX"
              )
       )
     ;; Получены все вычерченные плитки, повторно создать
     ;; структуру данных с правильным значением tileList и
     ;; восстановить свойство данных в реакторе.
     ;; Обновить плитки, связанные с границей полилинии.
     (setq NewReactorData
        (subst (cons 100 tileList)
           (assoc 100 NewReactorData)
           NewReactorData
           )
       )
     ;; Теперь имеются новые данные для полилинии.
     ;; Все, что осталось сделать, связать их с реактором
     ;; с помощью vlr-data-set.
     (vlr-data-set (car *reactorsToChange*) NewReactorData)
     ;; Удалить все ссылки на временные
     ;; переменные *polytochange* и *reactorsToChange*.
     (setq *polytochange* nil
       *reactorsToChange* nil
       )
     )
    )
  ;; Если возможно, удалить все элементы в глобальной переменной *Safe-to-Delete*!!!
  (Gp:Safe-Delete (car command-list))
  (princ)
  )


Обновление функции gp:Calculate-and-Draw-Tiles

Окончательная компоновка
Окончательная компоновка
Обновление функции gp:Calculate-and-Draw-Tiles

Ранее в этом занятии было отмечено, что необходимо вызвать функцию gp:Calculate-and-Draw-Tiles, чтобы использовать ActiveX для создания объектов, вызываемых из реактора функцией обратного вызова. Метод построения, выбранный пользователем (ActiveX, entmake или command), подавляется. Только что обновленный код функции gp:command-ended содержит следующее обращение к процедуре построения плиток:

(setq tileList (gp:Calculate-and-Draw-Tiles
    ;; список данных дорожки без корректного списка плиток.
    NewReactorData
    ;; Функция создания объектов.
    ;; В реакторе этой функцией *ДОЛЖЕН* быть ActiveX.
    "ActiveX"
   )
)

Функции gp:Calculate-and-Draw-Tiles передается два параметра: NewReactorData (список в форме исходного ассоциативного списка gp_PathData) и строка "ActiveX" (для задания стиля создания объекта). Взглянем на текущее определение функции gp:Calculate-and-Draw-Tiles. (Напомним, что эта функция определена в файле gpdraw.lsp.) Здесь приведена часть функции, которая объявляет параметры и локальные переменные:

(defun gp:Calculate-and-Draw-Tiles (BoundaryData /
     PathLength TileSpace
     TileRadius SpaceFilled
     SpaceToFill RowSpacing
     offsetFromCenter rowStartPoint
     pathWidth pathAngle
     ObjectCreationStyle TileList)

Заметим, что сейчас задан только один параметр, а переменная ObjectCreationStyle описана как локальная. Посмотрим, как присваивается значение переменной ObjectCreationStyle (далее в описании функции):

(setq ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData))))

Переменная ObjectCreationStyle определена внутри функции и извлекает значение переменной BoundaryData (ассоциативный список). Теперь нужно переопределить это значение.

Для добавления в функцию gp:Calculate-and-Draw-Tiles аргумента стиля создания объектов
  1. Добавьте переменную ObjectCreationStyle к аргументам функции.
  2. Удалите ObjectCreationStyle из списка локальных переменных.

    3. Выражение defun для функции должно выглядеть следующим образом:

    (defun gp:Calculate-and-Draw-Tiles (BoundaryData
                             ObjectCreationStyle
        / PathLength TileSpace
          TileRadius SpaceFilled
          SpaceToFile RowSpacing
          offsetFromCenter rowStartPoint
          pathWidth pathAngle
          TileList) ; удалить ObjectCreationStyle из локальных переменных

    Если переменная объявлена одновременно и параметром (до косой черты), и локальной переменной (после косой черты), VLISP сообщит об этом. Например, если описать ObjectCreationStyle одновременно как параметр и как переменную, а затем использовать средство проверки синтаксиса VLISP в функции gp:Calculate-and-Draw-Tiles, в окне "Сообщения сборки" появится следующее сообщение:

    ; *** ВНИМАНИЕ: один и тот же символ найден до и после / в списке аргументов: OBJECTCREATIONSTYLE
  3. Измените первое выражение setq в gp:Calculate-and-Draw-Tiles так, чтобы оно выглядело следующим образом (изменения обозначены полужирным шрифтом):
    4. (setq
       PathLength (cdr (assoc 41 BoundaryData))
       TileSpace (cdr (assoc 43 BoundaryData))
       TileRadius (cdr (assoc 42 BoundaryData))
       SpaceToFill (- PathLength TileRadius)
       RowSpacing (* (+ TileSpace (* TileRadius 2.0))
          (sin (Degrees->Radians 60))
       )
       SpaceFilled RowSpacing
       offsetFromCenter 0.0
       offsetDistance /(+(* TileRadius 2.0)TileSpace)2.0)
       rowStartPoint cdr (assoc 10 BoundaryData))
       pathWidth cdr (assoc 40 BoundaryData))
       pathAngle cdr (assoc 50 BoundaryData))
    ) ;_ end of setq
     (if (not ObjectCreationStyle)
       (setq ObjectCreationStyle (strcase (cdr (assoc 3 BoundaryData))))
    )

    Ранее стоявшее в тексте выражение, которое инициализировало переменную ObjectCreationStyle, теперь удалено. Программа проверяет, имеет ли параметр ObjectCreationStyle какое-либо значение. Если значение не задано (т.е. ObjectCreationStyle равно nil), функция присваивает параметру значение из переменной BoundaryData.

    Имеется еще одна серия изменений, которые необходимо выполнить в функции gp:Calculate-and-Draw-Tiles.


Изменение других вызовов функции gp:Calculate-and-Draw-Tiles

Окончательная компоновка
Окончательная компоновка
Изменение других вызовов функции gp:Calculate-and-Draw-Tiles

В функции обратного вызова реактора жестко назначенная строка "ActiveX" передается gp:Calculate-and-Draw-Tiles в виде аргумента ObjectCreationStyle. Однако, что же происходит при других вызовах функции gp:Calculate-and-Draw-Tiles?

Вспомним занятие 4, где говорилось, что при каждом изменении фиктивной функции необходимо ответить на следующие вопросы:

  • Был ли изменен вызов (обращение к) функции? Необходимо ли функции то же число аргументов, что и раньше?
  • Изменился ли результат, возвращаемый функцией?

На те же вопросы необходимо отвечать при каждом существенном изменении рабочей функции в процессе создания, улучшения и обновления приложений. В данном случае необходимо найти все другие функции, вызывающие gp:Calculate-and-Draw-Tiles. В VLISP имеется средство, помогающее это сделать.

Для нахождения всех вызовов функции gp:Calculate-and-Draw-Tiles в проекте
  1. В окне текстового редактора VLISP дважды нажмите кнопку мыши на слове gp:Calculate-and-Draw-Tiles в файле gpdraw.lsp.
  2. Выберите в меню VLISP "Поиск" » "Найти".

    3. Так как имя функции было выделено заранее, оно появляется в качестве строки для поиска.

  3. Установите переключатель "Поиск" в положение "Проект".

    4. При выборе этой опции диалоговое окно "Поиск" разворачивается, и в нем можно выбрать проект для поиска.

  4. Укажите имя проекта и нажмите кнопку "Найти".

    5. VLISP выводит результат в отдельном окне.

  5. Посмотрим на результаты в окне "Результат поиска" и определим, есть ли в коде другие фрагменты, выполняющие вызов функции gp:Calculate-and-Draw-Tiles. У нее должно быть только одно местоположение: файл gpmain.lsp.
  6. В окне "Результат поиска" нажмите дважды кнопку мыши на строке с кодом вызова функции gp:Calculate-and-Draw-Tiles.

    7. VLISP активизирует окно текстового редактора и переходит к строке gpmain.lsp. Пока код выглядит следующим образом:

    (setq tilelist (gp:Calculate-and-Draw-Tiles gp_PathData))
  7. Замените строку кода на следующую:
    8. (setq tilelist (gp:Calculate-and-Draw-Tiles gp_PathData nil))

    Почему nil? Еще раз посмотрим на алгоритм:

    If ObjectCreationStyle is nil, assign it from the BoundaryData.

    Если передать nil в качестве параметра в функцию gp:Calculate-and-Draw-Tiles, эта функция выполняет проверку выбора пользователем варианта вычерчивания плиток (как определено в диалоговом окне выбора и сохранено в gp_PathData). Однако при последующих вызовах функции реактором, обслуживающим окончание выполнения команды, будет использоваться метод ActiveX.

Отлично! Теперь реакторы выполняют свои основные функции. При желании можно скопировать файлы gpmain.lsp и gpdraw.lsp из папки Tutorial\VisualLISP\Lesson7 в рабочую папку и проверить окончательный отлаженный код.

Однако почивать на лаврах пока рано. Необходимо еще выполнить много работы с функцией gp:Command-ended:

(setq NewReactorData
    (gp:RedefinePolyBorder CurrentPoints reactorData)
) ;_ end of setq


Переопределение полилинии-границы

Окончательная компоновка
Окончательная компоновка
Переопределение полилинии-границы

В ходе работы мы познакомились с достаточным количеством новых концепций, терминов и функций. И все же рекомендуется, проработав изложенный материал, скопировать пример кода из папки файлов учебного пособия, а не вводить его самостоятельно.

Для копирования кода, переопределяющего полилинии-границы
  1. Скопируйте файл gppoly.lsp из папки Tutorial\VisualLISP\Lesson7 в рабочую папку.
  2. В окне проекта нажмите кнопку "Свойства проекта".
  3. Добавьте к проекту файл gppoly.lsp.
  4. Нажмите "OK" для добавления файла в проект.
  5. В окне проекта дважды нажмите кнопку мыши на имени gppoly.lsp, чтобы открыть файл.

Темы:

  • Изучение функций файла gppoly.lsp
  • Объяснение функции gp:RedefinePolyBorder
  • Описание функции gp:FindMovedPoint
  • Описание функции gp:FindPointInList
  • Описание функции gp:recalcPolyCorners
  • Описание функций gp:pointEqual, gp:rtos2 и gp:zeroSmallNum


Изучение функций файла gppoly.lsp

Окончательная компоновка
Окончательная компоновка
Изучение функций файла gppoly.lsp

Файл gppoly.lsp содержит ряд функций, необходимых для коррекции формы полилинии после перемещения одной из ее вершин. В данном учебном пособии подробно объясняются только некоторые из них.

Примечание В данном разделе учебного пособия построения парковой дорожки описан наиболее сложный материал. Начинающим программистам рекомендуется перейти к разделу Сборка приложения, расположенному ниже в данной главе.

Организация функций внутри файла gppoly.lsp аналогична другим исходным файлам AutoLISP. Функция самого высокого уровня, зачастую основная, или функция C: (в данном случае gp:Redefine-PolyBorder), расположена в конце файла. Функции, вызываемые из главной, описаны в исходном файле выше. Такой порядок сложился исторически, так как в прежние времена он использовался некоторыми средами программирования. В VLISP соблюдать такой порядок личное дело каждого программиста, так как требований располагать функции в какой-либо определенной последовательности не существует.

Перед тем как перейти к подробностям, вернемся немного назад и посмотрим, что нужно сделать, чтобы пересчитать и построить границу дорожки. На следующем чертеже показан пример парковой дорожки и отмечены ключевые точки ассоциативного списка, хранящегося с реактором:

В данном примере ключевая точка 12 представляет собой нижний левый угол, 13 — нижний правый, и т.д. Если пользователь перемещает правую верхнюю точку (ключевая точка 14), программе необходимо повторно рассчитать две существующих точки: нижнюю правую (13) и верхнюю левую (15).


Объяснение функции gp:RedefinePolyBorder

Окончательная компоновка
Окончательная компоновка
Объяснение функции gp:RedefinePolyBorder

Действие функции gp:RedefinePolyBorder словесно описывается следующим алгоритмом:

Функция gp:RedefinePolyBorder
   Извлечь предыдущие точки углов полилинии
     (ключевые значения 12, 13, 14 и 15).
   Найти перемещенную угловую точку, сравнив предыдущие точки
     углов полилинии с текущими угловыми точками.
     Несовпадающая точка и есть перемещенная точка.
   Задать новые угловые точки, пересчитав две точки,
     соседние с перемещенной.
   Обновить новые угловые точки в данных реактора (они будут
     сохранены с реактором для измененной полилинии).
   Обновить другую информацию в данных реактора. Необходимо пересчитать начальную
      и конечную точки, ширину и длину дорожки.


Описание функции gp:FindMovedPoint

Окончательная компоновка
Окончательная компоновка
Описание функции gp:FindMovedPoint

В функции gp:FindMovedPoint используются очень мощные LISP-выражения для работы со списками. Функция сравнивает список текущих точек полилинии (после перетаскивания пользователем одной из них) с предыдущими точками и возвращает кодированный список (13 <значение_x> <значение_y>) для перемещенной точки.

Наилучший способ познакомиться с работой функции — это выполнить ее пошагово и посмотреть значения, которыми она оперирует. Для этого надо задать точку останова в первом выражении (setq result . . .) и проследить, как изменяются при пошаговом выполнении функции следующие переменные:

  • KeyListToLookFor;
  • PresentPoints;
  • KeyedList;
  • Result;
  • KeyListStatus;
  • MissingKey;
  • MovedPoint.

Функции mapcar и lambda будут рассмотрены в следующем разделе. Для того чтобы понять, что происходит внутри функций, следует ознакомиться с комментариями к коду.


Описание функции gp:FindPointInList

Окончательная компоновка
Окончательная компоновка
Описание функции gp:FindPointInList

Заголовок функции в исходном коде поясняет, как функция gp:FindPointInList преобразует информацию, с которой она работает. Как и предыдущая функция Gp:FindMovedPoint, эта функция для выполнения работы использует возможности LISP по работе со списками. При работе со списками часто встречается комбинация функций mapcar и lambda. На первый взгляд, это несколько запутанные функции, имена которых не отображают то, что они в действительности делают. Однако они являются самыми мощными средствами из всего набора функций AutoLISP. Ниже приведено краткое описание функций mapcar и lambda.

Функция mapcar применяет выражение ко всем элементам списка. Например, если имеется список целых чисел 1, 2, 3 и 4, функцию mapcar можно использовать для прибавления единицы (выражение 1+) ко всем элементам списка:

_$ (mapcar '1+ '(1 2 3 4))

(2 3 4 5)

Исходное определение mapcar заключается в сопоставлении функции, заданной в первом параметре, со следующими элементами во втором параметре — списке. Результатом выполнения операции mapcar является преобразованный список. В действительности функция mapcar может использоваться не только для этого, но на данном этапе такого определения достаточно.

В приведенном примере для каждого значения в списке '(1 2 3 4) была выполнена функция 1+. По сути дела, функция mapcar выполнила несколько операций, а затем собрала полученные значения в один список:

(1+ 1) -> 2
(1+ 2) -> 3
(1+ 3) -> 4
(1+ 4) -> 5

Приведем еще один пример использования mapcar, на этот раз для выполнения функции null с целью выявления в списке неверных значений:

_$ (mapcar 'null (list 1 (= 3 "3") nil "Steve"))

(nil T T nil)

В данном фрагменте кода выполняется следующее:

(null 1) -> nil
(null (= 3 "3") -> T
(null nil) -> T
(null "Steve") -> nil

Внутри функции mapcar можно использовать большинство из функций AutoLISP. Допускается также применять пользовательские функции. Например, возьмем пользовательскую функцию с именем equals2:

_$ (defun equals2(num)(= num 2))

EQUALS2
_$ (mapcar 'equals2 '(1 2 3 4))

(nil T nil nil)

Однако, функцию equals2 можно и не описывать отдельно. В таких случаях может пригодиться функция lambda. Она служит для того, чтобы создавать функции "на ходу"; другими словами lambda описывает анонимные функции. Иногда lambda определяется как анонимная функция. Например, вместо описания функции equals2можно использовать выражение lambda для выполнения той же операции:

_$ (mapcar '(lambda (num) (= num 2)) '(1 2 3 4))

(nil T nil nil)

В данном фрагменте кода выполняется следующее:

(= 1 2) -> nil
(= 2 2) -> T
(= 3 2) -> nil
(= 4 2) -> nil

После этих пояснений работа функции gp:FindPointInList становится понятной. Снова обратимся к комментариям внутри исходного кода.


Описание функции gp:recalcPolyCorners

Окончательная компоновка
Окончательная компоновка
Описание функции gp:recalcPolyCorners

Для понимания работы функции gp:recalcPolyCorners вспомним, что означают закодированные значения от 12 до 15.

В изображенном на чертеже случае угловая точка с кодом 14 была перемещена пользователем. Это означает, что необходимо пересчитать угловые точки 13 и 15.

Точку 15 необходимо передвинуть вдоль текущего вектора, определенного точками 12 и 15, до тех пор, пока она не выровняется с новой точкой 14. Векторы от 12 к 15 и от 14 к 15 должны быть перпендикулярны друг другу. То же самое необходимо сделать для вычисления нового положения точки 13.

Посмотрите еще раз на программный код функции. Теперь он должен стать более понятным.


Описание функций gp:pointEqual, gp:rtos2 и gp:zeroSmallNum

Окончательная компоновка
Окончательная компоновка
Описание функций gp:pointEqual, gp:rtos2 и gp:zeroSmallNum

Эти три функции позволяют учитывать особенности программирования в системе AutoCAD, которая, как известно, позволяет обеспечивать большую точность. Иногда, однако, величины бывают недостаточно точными из-за операций округления значений с плавающей точкой, задающих геометрическое положение. Это необходимо иметь в виду при сравнении одного набора точек с другим.

В некоторых случаях при выводе информации, связанной с AutoCAD, можно встретить такие малые значения, как, например, 1.0e-017. Это число почти равно нулю, однако при сравнении его с нулем даже такое малое несовпадение играет роль.

В приложении построения парковой дорожки при сравнении чисел необходимо учитывать все сказанное выше. Функции gp:pointEqual, gp:rtos2 и gp:zeroSmallNum как раз и служат для правильной обработки различий, возникающих из-за округления, при сравнении списков.

На этом обзор функций файла gppoly.lsp завершается.


Окончательный код программы

Окончательная компоновка
Окончательная компоновка
Окончательный код программы

На этом занятии было выполнено следующее:

  • Функция gp:drawOutline изменена таким образом, что помимо указателей на полилинию возвращаются и точки периметра полилинии. Эта информация была добавлена к переменной gp_PathData. Переменная хранится с объектным реактором, назначенным каждой парковой дорожке.
  • Обновлена функция реактора в файле gpreact.lsp.
  • Добавлены функции xyzList->ListOfPoints, xyList->ListOfPoints и другие служебные функции в файле utils.lsp.
  • Обновлена функция gp:Calculate-and-Draw-Tiles; ObjectCreationStyle преобразована в ней из локальной переменной в параметр.
  • Изменен вызов функции gp:Calculate-and-Draw-Tiles из функции C:GPath (файл gpmain.lsp).
  • К проекту добавлен файл gppoly.lsp, а затем рассмотрены функции, входящие в него.

Теперь можно проверить готовое приложение. Для этого надо сохранить результаты работы, загрузить исходные файлы проекта, выполнить функцию Gpath и попробовать растянуть и передвинуть границу построенной парковой дорожки. Следует помнить: если что-то не работает и программу не удается отладить, необходимо загрузить готовый код из папки Tutorial\VisualLISP\Lesson7.


Сборка приложения

Окончательная компоновка
Окончательная компоновка
Сборка приложения

На последнем этапе данного учебного пособия код программы построения парковой дорожки преобразуем в отдельное приложение. В этом виде его можно предлагать как отдельный исполняемый файл различным пользователям и клиентам. К счастью, эта последняя задача является, вероятно, самой легкой во всем учебном пособии, так как VLISP выполняет практически всю работу самостоятельно.

Примечание Рекомендуется использовать для сборки приложения только отлаженный код. Перед этим следует убедиться, что работа исходных файлов приложения была проверена и получены удовлетворительные результаты.

Темы:

  • Запуск Мастера сборки приложений


Запуск Мастера сборки приложений

Для создания отдельных приложений в VLISP используется Мастер сборки приложений.

Запуск Мастера сборки приложений
  1. Для запуска Мастера выберите в меню VLISP "Файл" » "Собрать приложение" » "Мастер новых приложений".
  2. Выберите режим "Сложный" и нажмите "Далее".

    3. Мастер предлагает указать папку для хранения файлов, созданных при сборке, и назначить приложению имя. В результате использования команды "Собрать приложение" создаются два выходных файла: a .vlx, являющийся исполняемым, и файл .prv, содержащий параметры, заданные при сборке приложения. Расширение имени файла .prv называется также файлом сборки. Файлы сборки можно использовать, если в этом возникнет необходимость, для перекомпиляции приложения.

  3. Укажите для приложения папку Tutorial\VisualLISP\MyPath и присвойте приложению имя gardenpath. VLISP использует имя приложения для создаваемых файлов (в данном случае, gardenpath.vlx и gardenpath.prv).

    4. Нажмите "Далее" для продолжения.

  4. Параметры приложений в данном учебном пособии не рассматриваются. Используйте стандартные настройки и нажмите "Далее". (Более подробную информацию о приложениях с отдельным пространством имен см. в разделе “Running an Application in Its Own Namespace” ("Запуск приложения в собственном пространстве имен") в документе AutoLISP Developer's Guide ("Руководство разработчика AutoLISP").)
  5. На следующем этапе Мастер предлагает указать все исходные файлы AutoLISP, из которых создается приложение. Можно задавать исходные LISP-файлы по отдельности, однако есть и более простой путь. Измените раскрывающийся список выбора типа файлов, чтобы отображался элемент “Файл проекта Visual LISP”, затем нажмите кнопку "Добавить". Выберите файл проекта Gpath и нажмите "Открыть".

    6. Примечание При работе с учебным пособием могло быть создано несколько файлов проектов Gpath. Выберите самый последний из них. Если исходный код был скопирован из папки 7 занятия, то файл проекта будет иметь имя Gpath7.prj.

    Выбрав файл проекта, нажмите "Далее".

    Одним из преимуществ скомпилированных приложений является то, что в них можно включать файлы диалоговых окон (.dcl). Это позволяет сократить число отдельных исходных файлов, с которыми придется работать пользователю, а также исключить проблемы с поиском DCL-файлов при загрузке диалоговых окон.

  6. Измените раскрывающийся список выбора типа файлов, чтобы отображался элемент “DCL-файлы”, затем нажмите кнопку "Добавить". Выберите файл gpdialog.dcl и нажмите "Открыть".

    7. Нажмите "Далее" для продолжения сборки приложения.

  7. Параметры компиляции в данном учебном пособии не рассматриваются. Используйте стандартные настройки и нажмите "Далее". (Подробнее о параметрах компилирования см. в разделе “Optimizing Application Code” ("Оптимизация кода приложений") в документе AutoLISP Developer's Guide ("Руководство разрабтчика AutoLISP")).
  8. Окончательный этап используется для просмотра выбранных элементов. Теперь нажмите кнопку "Завершить". VLISP приступает к сборке. В окне "Сообщения сборки" выводится информация о ходе выполнения процесса. Во время сборки создается несколько вспомогательных файлов, соответствующих отдельным исходным файлам, которые затем объединяются в одно приложение VLX.

Результатом сборки является исполняемый файл с именем gardenpath.vlx. Для его проверки необходимо выполнить следующее:

  • В меню "Сервис" AutoCAD выберите "Приложения".
  • Загрузите только что созданное приложение gardenpath.vlx из папки Tutorial\VisualLISP\MyPath.
  • Выполните команду gpath.


Итоги учебного пособия

Окончательная компоновка
Окончательная компоновка
Итоги учебного пособия

Наконец-то мы дошли до конца дорожки! При этом нами было изучено много нового. Мы ознакомились с концепциями AutoLISP и их применением в VLISP. Занятие “Просмотр парковой дорожки” дает общее представление о многих вопросах. Возможно, некоторые пользователи пожелают изучить их подробнее. Ниже приведен краткий список литературы по LISP и AutoLISP.


Книги по LISP и AutoLISP

Окончательная компоновка
Окончательная компоновка
Книги по LISP и AutoLISP

Understanding AutoLISP: Programming for Productivity, William Kramer, Autodesk Press, ISBN 0-8273-5832-6.

AutoLISP in Plain English: A Practical Guide for Non-Programmers, George O. Head, Ventana Press, ISBN: 1566041406.

LISP, 3rd Edition, Patrick Henry Winston and Berthold Klaus Paul Horn, Addison-Wesley Publishing Company, ISBN 0-201-08319-1.

ANSI Common Lisp, Paul Graham, Prentice Hall, ISBN 0-13-370875-6.

Common LISP, The Language, Second Edition, Guy L. Steele, Jr., Digital Press, ISBN 1-55558-041-6.


Комментарии пользователя

Данная форма предназначена для ввода замечаний и предложений, которые будут отправлены в группу технической документации компании Autodesk.

Заголовок и расположение раздела (не изменять):
Имя и фамилия (не обязательно):
         E-mail (не обязательно):
Текст комментария:

Замечание: Форма не может служить средством получения советов или технической поддержки. Информация из нее будет использована для улучшения документации по продукту. Благодарим Вас!